/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gc/scoped_gc_critical_section.h"
#include "instrumentation.h"
#include "runtime.h"
#include "runtime_callbacks.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
#include "thread_list.h"

namespace tracefast {

#if ((!defined(TRACEFAST_INTERPRETER) && !defined(TRACEFAST_TRAMPOLINE)) || \
     (defined(TRACEFAST_INTERPRETER) && defined(TRACEFAST_TRAMPOLINE)))
#error Must set one of TRACEFAST_TRAMPOLINE or TRACEFAST_INTERPRETER during build
#endif


#ifdef TRACEFAST_INTERPRETER
static constexpr const char* kTracerInstrumentationKey = "tracefast_INTERPRETER";
static constexpr bool kNeedsInterpreter = true;
#else  // defined(TRACEFAST_TRAMPOLINE)
static constexpr const char* kTracerInstrumentationKey = "tracefast_TRAMPOLINE";
static constexpr bool kNeedsInterpreter = false;
#endif  // TRACEFAST_INITERPRETER

class Tracer final : public art::instrumentation::InstrumentationListener {
 public:
  Tracer() {}

  void MethodEntered(art::Thread* thread ATTRIBUTE_UNUSED,
                     art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                     art::ArtMethod* method ATTRIBUTE_UNUSED,
                     uint32_t dex_pc ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void MethodExited(art::Thread* thread ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                    art::ArtMethod* method ATTRIBUTE_UNUSED,
                    uint32_t dex_pc ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> return_value ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void MethodExited(art::Thread* thread ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                    art::ArtMethod* method ATTRIBUTE_UNUSED,
                    uint32_t dex_pc ATTRIBUTE_UNUSED,
                    const art::JValue& return_value ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void MethodUnwind(art::Thread* thread ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                    art::ArtMethod* method ATTRIBUTE_UNUSED,
                    uint32_t dex_pc ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void DexPcMoved(art::Thread* thread ATTRIBUTE_UNUSED,
                  art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                  art::ArtMethod* method ATTRIBUTE_UNUSED,
                  uint32_t new_dex_pc ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void FieldRead(art::Thread* thread ATTRIBUTE_UNUSED,
                 art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                 art::ArtMethod* method ATTRIBUTE_UNUSED,
                 uint32_t dex_pc ATTRIBUTE_UNUSED,
                 art::ArtField* field ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void FieldWritten(art::Thread* thread ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                    art::ArtMethod* method ATTRIBUTE_UNUSED,
                    uint32_t dex_pc ATTRIBUTE_UNUSED,
                    art::ArtField* field ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> field_value ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void FieldWritten(art::Thread* thread ATTRIBUTE_UNUSED,
                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
                    art::ArtMethod* method ATTRIBUTE_UNUSED,
                    uint32_t dex_pc ATTRIBUTE_UNUSED,
                    art::ArtField* field ATTRIBUTE_UNUSED,
                    const art::JValue& field_value ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void ExceptionThrown(art::Thread* thread ATTRIBUTE_UNUSED,
                       art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void ExceptionHandled(art::Thread* self ATTRIBUTE_UNUSED,
                        art::Handle<art::mirror::Throwable> throwable ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void Branch(art::Thread* thread ATTRIBUTE_UNUSED,
              art::ArtMethod* method ATTRIBUTE_UNUSED,
              uint32_t dex_pc ATTRIBUTE_UNUSED,
              int32_t dex_pc_offset ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

  void WatchedFramePop(art::Thread* thread ATTRIBUTE_UNUSED,
                       const art::ShadowFrame& frame ATTRIBUTE_UNUSED)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) { }

 private:
  DISALLOW_COPY_AND_ASSIGN(Tracer);
};

Tracer gEmptyTracer;

static void StartTracing() REQUIRES(!art::Locks::mutator_lock_,
                                    !art::Locks::thread_list_lock_,
                                    !art::Locks::thread_suspend_count_lock_) {
  art::Thread* self = art::Thread::Current();
  art::Runtime* runtime = art::Runtime::Current();
  art::gc::ScopedGCCriticalSection gcs(self,
                                       art::gc::kGcCauseInstrumentation,
                                       art::gc::kCollectorTypeInstrumentation);
  art::ScopedSuspendAll ssa("starting fast tracing");
  runtime->GetInstrumentation()->AddListener(&gEmptyTracer,
                                             art::instrumentation::Instrumentation::kMethodEntered |
                                             art::instrumentation::Instrumentation::kMethodExited |
                                             art::instrumentation::Instrumentation::kMethodUnwind);
  runtime->GetInstrumentation()->EnableMethodTracing(kTracerInstrumentationKey, kNeedsInterpreter);
}

class TraceFastPhaseCB : public art::RuntimePhaseCallback {
 public:
  TraceFastPhaseCB() {}

  void NextRuntimePhase(art::RuntimePhaseCallback::RuntimePhase phase)
      override REQUIRES_SHARED(art::Locks::mutator_lock_) {
    if (phase == art::RuntimePhaseCallback::RuntimePhase::kInit) {
      art::ScopedThreadSuspension sts(art::Thread::Current(),
                                      art::ThreadState::kWaitingForMethodTracingStart);
      StartTracing();
    }
  }
};
TraceFastPhaseCB gPhaseCallback;

// The plugin initialization function.
extern "C" bool ArtPlugin_Initialize() REQUIRES_SHARED(art::Locks::mutator_lock_) {
  art::Runtime* runtime = art::Runtime::Current();
  art::ScopedThreadSuspension stsc(art::Thread::Current(),
                                   art::ThreadState::kWaitingForMethodTracingStart);
  art::ScopedSuspendAll ssa("Add phase callback");
  runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback);
  return true;
}

extern "C" bool ArtPlugin_Deinitialize() {
  // Don't need to bother doing anything.
  return true;
}

}  // namespace tracefast