/* Copyright (C) 2017 The Android Open Source Project
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This file implements interfaces from the file jvmti.h. This implementation
 * is licensed under the same terms as the file jvmti.h.  The
 * copyright and license information for the file jvmti.h follows.
 *
 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#ifndef ART_OPENJDKJVMTI_DEOPT_MANAGER_H_
#define ART_OPENJDKJVMTI_DEOPT_MANAGER_H_

#include <atomic>
#include <iosfwd>
#include <unordered_map>

#include "base/mutex.h"
#include "runtime_callbacks.h"

#include <jvmti.h>

namespace art {
class ArtMethod;
class ScopedObjectAccessUnchecked;
namespace mirror {
class Class;
}  // namespace mirror
}  // namespace art

namespace openjdkjvmti {

class DeoptManager;

struct JvmtiMethodInspectionCallback : public art::MethodInspectionCallback {
 public:
  explicit JvmtiMethodInspectionCallback(DeoptManager* manager) : manager_(manager) {}

  bool IsMethodBeingInspected(art::ArtMethod* method)
      override REQUIRES_SHARED(art::Locks::mutator_lock_);

  bool IsMethodSafeToJit(art::ArtMethod* method)
      override REQUIRES_SHARED(art::Locks::mutator_lock_);

  bool MethodNeedsDebugVersion(art::ArtMethod* method)
      override REQUIRES_SHARED(art::Locks::mutator_lock_);

 private:
  DeoptManager* manager_;
};

class ScopedDeoptimizationContext;

class DeoptManager {
 public:
  DeoptManager();

  void Setup();
  void Shutdown();

  void DumpDeoptInfo(art::Thread* self, std::ostream& stream);

  void RemoveDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_,
                                                !art::Roles::uninterruptible_);
  void AddDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_,
                                             !art::Roles::uninterruptible_);
  bool MethodHasBreakpoints(art::ArtMethod* method)
      REQUIRES(!deoptimization_status_lock_);

  void RemoveMethodBreakpoint(art::ArtMethod* method)
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);

  void AddMethodBreakpoint(art::ArtMethod* method)
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);

  void AddDeoptimizeAllMethods()
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);

  void RemoveDeoptimizeAllMethods()
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);

  jvmtiError AddDeoptimizeThreadMethods(art::ScopedObjectAccessUnchecked& soa, jthread thread)
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);

  jvmtiError RemoveDeoptimizeThreadMethods(art::ScopedObjectAccessUnchecked& soa, jthread thread)
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);

  void DeoptimizeThread(art::Thread* target)
      REQUIRES(!art::Locks::thread_list_lock_)
      REQUIRES_SHARED(art::Locks::mutator_lock_);
  void DeoptimizeAllThreads() REQUIRES_SHARED(art::Locks::mutator_lock_);

  void FinishSetup()
      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
      REQUIRES(art::Locks::mutator_lock_);

  static DeoptManager* Get();

  bool HaveLocalsChanged() const {
    return set_local_variable_called_.load();
  }

  void SetLocalsUpdated() {
    set_local_variable_called_.store(true);
  }

 private:
  bool MethodHasBreakpointsLocked(art::ArtMethod* method)
      REQUIRES(breakpoint_status_lock_);

  // Wait until nothing is currently in the middle of deoptimizing/undeoptimizing something. This is
  // needed to ensure that everything is synchronized since threads need to drop the
  // deoptimization_status_lock_ while deoptimizing methods.
  void WaitForDeoptimizationToFinish(art::Thread* self)
      RELEASE(deoptimization_status_lock_) REQUIRES(!art::Locks::mutator_lock_);

  void WaitForDeoptimizationToFinishLocked(art::Thread* self)
      REQUIRES(deoptimization_status_lock_, !art::Locks::mutator_lock_);

  void AddDeoptimizeAllMethodsLocked(art::Thread* self)
      RELEASE(deoptimization_status_lock_)
      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);

  void RemoveDeoptimizeAllMethodsLocked(art::Thread* self)
      RELEASE(deoptimization_status_lock_)
      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);

  void PerformGlobalDeoptimization(art::Thread* self)
      RELEASE(deoptimization_status_lock_)
      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);

  void PerformGlobalUndeoptimization(art::Thread* self)
      RELEASE(deoptimization_status_lock_)
      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);

  void PerformLimitedDeoptimization(art::Thread* self, art::ArtMethod* method)
      RELEASE(deoptimization_status_lock_)
      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);

  void PerformLimitedUndeoptimization(art::Thread* self, art::ArtMethod* method)
      RELEASE(deoptimization_status_lock_)
      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);

  static constexpr const char* kDeoptManagerInstrumentationKey = "JVMTI_DeoptManager";

  art::Mutex deoptimization_status_lock_ ACQUIRED_BEFORE(art::Locks::classlinker_classes_lock_);
  art::ConditionVariable deoptimization_condition_ GUARDED_BY(deoptimization_status_lock_);
  bool performing_deoptimization_ GUARDED_BY(deoptimization_status_lock_);

  // Number of times we have gotten requests to deopt everything.
  uint32_t global_deopt_count_ GUARDED_BY(deoptimization_status_lock_);

  // Number of users of deoptimization there currently are.
  uint32_t deopter_count_ GUARDED_BY(deoptimization_status_lock_);

  // A mutex that just protects the breakpoint-status map. This mutex should always be at the
  // bottom of the lock hierarchy. Nothing more should be locked if we hold this.
  art::Mutex breakpoint_status_lock_ ACQUIRED_BEFORE(art::Locks::abort_lock_);
  // A map from methods to the number of breakpoints in them from all envs.
  std::unordered_map<art::ArtMethod*, uint32_t> breakpoint_status_
      GUARDED_BY(breakpoint_status_lock_);

  // The MethodInspectionCallback we use to tell the runtime if we care about particular methods.
  JvmtiMethodInspectionCallback inspection_callback_;

  // Set to true if anything calls SetLocalVariables on any thread since we need to be careful about
  // OSR after this.
  std::atomic<bool> set_local_variable_called_;

  // Helper for setting up/tearing-down for deoptimization.
  friend class ScopedDeoptimizationContext;
};

}  // namespace openjdkjvmti
#endif  // ART_OPENJDKJVMTI_DEOPT_MANAGER_H_
