// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_COMPILER_COMPILER_TIMINGS_H_
#define RUNTIME_VM_COMPILER_COMPILER_TIMINGS_H_

#include <memory>

#include "platform/allocation.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/thread.h"
#include "vm/timer.h"

#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif  // defined(DART_PRECOMPILED_RUNTIME)

#define PRECOMPILER_TIMERS_LIST(V)                                             \
  V(CompileAll)                                                                \
  V(Iterate)                                                                   \
  V(CompileFunction)                                                           \
  V(AddCalleesOf)                                                              \
  V(CheckForNewDynamicFunctions)                                               \
  V(CollectCallbackFields)                                                     \
  V(PrecompileConstructors)                                                    \
  V(AttachOptimizedTypeTestingStub)                                            \
  V(TraceForRetainedFunctions)                                                 \
  V(FinalizeDispatchTable)                                                     \
  V(ReplaceFunctionStaticCallEntries)                                          \
  V(Drop)                                                                      \
  V(Obfuscate)                                                                 \
  V(Dedup)                                                                     \
  V(SymbolsCompact)

#define INLINING_TIMERS_LIST(V)                                                \
  V(CollectGraphInfo)                                                          \
  V(PopulateWithICData)                                                        \
  V(FindCallSites)                                                             \
  V(SetInliningIdAndTryIndex)                                                  \
  V(MakeInliningDecision)                                                      \
  V(CheckForPragma)                                                            \
  V(InlineCall)                                                                \
  V(InlineRecognizedMethod)                                                    \
  V(DiscoverBlocks)                                                            \
  V(BuildDecisionGraph)                                                        \
  V(PrepareGraphs)

// Note: COMPILER_PASS_LIST must be the first element of the list below because
// we expect that pass ids are the same as ids of corresponding timers.
#define COMPILER_TIMERS_LIST(V)                                                \
  COMPILER_PASS_LIST(V)                                                        \
  PRECOMPILER_TIMERS_LIST(V)                                                   \
  INLINING_TIMERS_LIST(V)                                                      \
  V(BuildGraph)                                                                \
  V(EmitCode)                                                                  \
  V(FinalizeCode)

namespace dart {

// |CompilerTimings| provides a way to track time taken by various compiler
// passes via a fixed number of timers (specified in |COMPILER_TIMERS_LIST|).
//
// It supports arbitrary nesting of timers e.g. if |DiscoverBlocks| is invoked
// within two different compiler passes like |IfConvert| and |BranchSimplify|
// then |CompilerTimings| will separate these two invocations and measure each
// separately.
class CompilerTimings : public MallocAllocated {
 private:
#define INC(Name) +1
  static constexpr intptr_t kNumTimers = 0 COMPILER_TIMERS_LIST(INC);
#undef INC

  struct Timers : public MallocAllocated {
    Timer timers_[kNumTimers];
    std::unique_ptr<Timers> nested_[kNumTimers];
  };

 public:
  enum TimerId {
#define DECLARE_TIMER_ID(Name) k##Name,
    COMPILER_TIMERS_LIST(DECLARE_TIMER_ID)
#undef DECLARE_TIMER_ID
  };

  // Timing scope which starts and stop the timer with the given |id|.
  class Scope : public StackResource {
   public:
    Scope(Thread* thread, TimerId id)
        : StackResource(thread), stats_(thread->compiler_timings()) {
      if (stats_ != nullptr) {
        outer_nested_ = stats_->nested_;
        if (*outer_nested_ == nullptr) {
          // Created array of nested timers if we don't have one yet.
          *outer_nested_ = std::make_unique<Timers>();
        }

        timer_ = &(*outer_nested_)->timers_[id];
        stats_->nested_ = &(*outer_nested_)->nested_[id];

        timer_->Start();
      }
    }

    ~Scope() {
      if (stats_ != nullptr) {
        timer_->Stop();
        stats_->nested_ = outer_nested_;
      }
    }

   private:
    CompilerTimings* const stats_;
    Timer* timer_;
    std::unique_ptr<Timers>* outer_nested_;
  };

  CompilerTimings() { total_.Start(); }

  void RecordInliningStatsByOutcome(bool success, const Timer& timer) {
    if (success) {
      try_inlining_success_.AddTotal(timer);
    } else {
      try_inlining_failure_.AddTotal(timer);
    }
  }

  void Print();

 private:
  void PrintTimers(Zone* zone,
                   const std::unique_ptr<CompilerTimings::Timers>& timers,
                   const Timer& total,
                   intptr_t level);

  Timer total_;
  std::unique_ptr<Timers> root_ = std::make_unique<Timers>();

  // Timers nested under the currently running timer(s).
  std::unique_ptr<Timers>* nested_ = &root_;

  Timer try_inlining_success_;
  Timer try_inlining_failure_;
};

#define TIMER_SCOPE_NAME2(counter) timer_scope_##counter
#define TIMER_SCOPE_NAME(counter) TIMER_SCOPE_NAME2(counter)

#define COMPILER_TIMINGS_TIMER_SCOPE(thread, timer_id)                         \
  CompilerTimings::Scope TIMER_SCOPE_NAME(__COUNTER__)(                        \
      thread, CompilerTimings::k##timer_id)

#define COMPILER_TIMINGS_PASS_TIMER_SCOPE(thread, pass_id)                     \
  CompilerTimings::Scope TIMER_SCOPE_NAME(__COUNTER__)(                        \
      thread, static_cast<CompilerTimings::TimerId>(pass_id))

#define PRECOMPILER_TIMER_SCOPE(precompiler, timer_id)                         \
  CompilerTimings::Scope TIMER_SCOPE_NAME(__COUNTER__)(                        \
      (precompiler) -> thread(), CompilerTimings::k##timer_id)

}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_COMPILER_TIMINGS_H_
