// Copyright (c) 2013, 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.

#include "platform/assert.h"

#include "platform/thread_sanitizer.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_api_state.h"
#include "vm/flags.h"
#include "vm/globals.h"
#include "vm/profiler.h"
#include "vm/profiler_service.h"
#include "vm/source_report.h"
#include "vm/symbols.h"
#include "vm/unit_test.h"

namespace dart {

#ifndef PRODUCT

DECLARE_FLAG(bool, profile_vm);
DECLARE_FLAG(bool, profile_vm_allocation);
DECLARE_FLAG(int, max_profile_depth);
DECLARE_FLAG(int, optimization_counter_threshold);

static void ExpectUnoptimizedOrBytecode(dart::Expect expect,
                                        const char* expected,
                                        const char* got) {
#if defined(TESTING)
  TextBuffer buffer(1024);
  if (FLAG_interpreter) {
    buffer.AddString("[Bytecode] ");
  } else {
    buffer.AddString("[Unoptimized] ");
  }
  buffer.Printf("%s", expected);
  expect.StringEquals(buffer.buffer(), got);
#endif
}

#define EXPECT_UNOPTIMIZED_OR_BYTECODE(expected, got)                          \
  ExpectUnoptimizedOrBytecode(dart::Expect(__FILE__, __LINE__), (expected),    \
                              (got))

// SampleVisitor ignores samples with timestamp == 0.
const int64_t kValidTimeStamp = 1;

// SampleVisitor ignores samples with pc == 0.
const uword kValidPc = 0xFF;

NO_SANITIZE_THREAD
static void SetIgnoreRace(bool* flag, bool value) {
  *flag = value;
}

// Some tests are written assuming native stack trace profiling is disabled.
class DisableNativeProfileScope : public ValueObject {
 public:
  DisableNativeProfileScope()
      : FLAG_profile_vm_(FLAG_profile_vm),
        FLAG_profile_vm_allocation_(FLAG_profile_vm_allocation) {
    // Ignore race: we generally don't try to ensure the profiler is disabled
    // when changing setting for unit tests. On Linux/Android, there is no way
    // to wait for outstanding SIGPROFs.
    SetIgnoreRace(&FLAG_profile_vm, false);
    SetIgnoreRace(&FLAG_profile_vm_allocation, false);
  }

  ~DisableNativeProfileScope() {
    SetIgnoreRace(&FLAG_profile_vm, FLAG_profile_vm_);
    SetIgnoreRace(&FLAG_profile_vm_allocation, FLAG_profile_vm_allocation_);
  }

 private:
  const bool FLAG_profile_vm_;
  const bool FLAG_profile_vm_allocation_;
};

// Temporarily adjust the maximum profile depth.
class MaxProfileDepthScope : public ValueObject {
 public:
  explicit MaxProfileDepthScope(intptr_t new_max_depth)
      : saved_config_(Profiler::CurrentConfig()) {
    auto new_config = saved_config_;
    new_config.max_depth = new_max_depth;
    Profiler::SetConfig(new_config);
  }

  ~MaxProfileDepthScope() { Profiler::SetConfig(saved_config_); }

 private:
  const Profiler::Config saved_config_;
};

class ProfileSampleBufferTestHelper : public SampleVisitor {
 public:
  explicit ProfileSampleBufferTestHelper(Dart_Port port)
      : SampleVisitor(port) {}

  void VisitSample(Sample* sample) { sum_ += sample->At(0); }

  void Reset() {
    sum_ = 0;
    SampleVisitor::Reset();
  }

  intptr_t sum() const { return sum_; }

 private:
  intptr_t sum_ = 0;
};

static void VisitSamples(SampleBlockBuffer* buffer, SampleVisitor* visitor) {
  visitor->Reset();
  buffer->VisitSamples(visitor);
}

class SampleBlockBufferOverrideScope {
 public:
  explicit SampleBlockBufferOverrideScope(SampleBlockBuffer* buffer)
      : override_(buffer) {
    orig_ = Profiler::sample_block_buffer();
    Profiler::set_sample_block_buffer(override_);
  }

  ~SampleBlockBufferOverrideScope() {
    Profiler::set_sample_block_buffer(orig_);
    delete override_;
  }

 private:
  SampleBlockBuffer* orig_;
  SampleBlockBuffer* override_;
};

TEST_CASE(Profiler_SampleBufferWrapTest) {
  Isolate* isolate = Isolate::Current();

  SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1));
  SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();

  Dart_Port i = 123;
  ProfileSampleBufferTestHelper visitor(i);

  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(0, visitor.sum());
  Sample* s;

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, 2);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(2, visitor.sum());

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, 4);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(6, visitor.sum());

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, 6);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(12, visitor.sum());

  // Mark the completed blocks as free so they can be re-used.
  sample_buffer->FreeCompletedBlocks();

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, 8);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(18, visitor.sum());

  // The overridden sample buffer will be freed before isolate shutdown.
  isolate->set_current_sample_block(nullptr);
}

TEST_CASE(Profiler_SampleBufferIterateTest) {
  Isolate* isolate = Isolate::Current();

  SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1));
  SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();

  Dart_Port i = 123;
  ProfileSampleBufferTestHelper visitor(i);

  sample_buffer->VisitSamples(&visitor);
  EXPECT_EQ(0, visitor.visited());
  Sample* s;
  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, kValidPc);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(1, visitor.visited());

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, kValidPc);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(2, visitor.visited());

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, kValidPc);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(3, visitor.visited());

  s = sample_buffer->ReserveCPUSample(isolate);
  EXPECT_NOTNULL(s);
  s->Init(i, kValidTimeStamp, 0);
  s->SetAt(0, kValidPc);
  VisitSamples(sample_buffer, &visitor);
  EXPECT_EQ(3, visitor.visited());

  // The overridden sample buffer will be freed before isolate shutdown.
  isolate->set_current_sample_block(nullptr);
}

TEST_CASE(Profiler_AllocationSampleTest) {
  Isolate* isolate = Isolate::Current();
  SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(1, 1);
  Sample* sample = sample_buffer->ReserveAllocationSample(isolate);
  sample->Init(isolate->main_port(), 0, 0);
  sample->set_metadata(99);
  sample->set_is_allocation_sample(true);
  EXPECT_EQ(99, sample->allocation_cid());
  isolate->set_current_allocation_sample_block(nullptr);
  delete sample_buffer;
}

static LibraryPtr LoadTestScript(const char* script) {
  Dart_Handle api_lib;
  {
    TransitionVMToNative transition(Thread::Current());
    api_lib = TestCase::LoadTestScript(script, nullptr);
    EXPECT_VALID(api_lib);
  }
  Library& lib = Library::Handle();
  lib ^= Api::UnwrapHandle(api_lib);
  return lib.ptr();
}

static ClassPtr GetClass(const Library& lib, const char* name) {
  Thread* thread = Thread::Current();
  const Class& cls = Class::Handle(
      lib.LookupClassAllowPrivate(String::Handle(Symbols::New(thread, name))));
  EXPECT(!cls.IsNull());  // No ambiguity error expected.
  return cls.ptr();
}

static FunctionPtr GetFunction(const Library& lib, const char* name) {
  Thread* thread = Thread::Current();
  const Function& func = Function::Handle(lib.LookupFunctionAllowPrivate(
      String::Handle(Symbols::New(thread, name))));
  EXPECT(!func.IsNull());  // No ambiguity error expected.
  return func.ptr();
}

static void Invoke(const Library& lib,
                   const char* name,
                   intptr_t argc = 0,
                   Dart_Handle* argv = nullptr) {
  Thread* thread = Thread::Current();
  Dart_Handle api_lib = Api::NewHandle(thread, lib.ptr());
  TransitionVMToNative transition(thread);
  {
    SetFlagScope<bool> sfs(&FLAG_verify_entry_points, false);
    Dart_Handle result = Dart_Invoke(api_lib, NewString(name), argc, argv);
    EXPECT_VALID(result);
  }
}

class AllocationFilter : public SampleFilter {
 public:
  AllocationFilter(Dart_Port port,
                   intptr_t cid,
                   int64_t time_origin_micros = -1,
                   int64_t time_extent_micros = -1)
      : SampleFilter(port,
                     Thread::kMutatorTask,
                     time_origin_micros,
                     time_extent_micros),
        cid_(cid),
        enable_vm_ticks_(false) {}

  bool FilterSample(Sample* sample) {
    if (!enable_vm_ticks_ && (sample->vm_tag() == VMTag::kVMTagId)) {
      // We don't want to see embedder ticks in the test.
      return false;
    }
    return sample->is_allocation_sample() && (sample->allocation_cid() == cid_);
  }

  void set_enable_vm_ticks(bool enable) { enable_vm_ticks_ = enable; }

 private:
  intptr_t cid_;
  bool enable_vm_ticks_;
};

class ProfileStackWalker {
 public:
  explicit ProfileStackWalker(Profile* profile, bool as_func = false)
      : profile_(profile),
        as_functions_(as_func),
        index_(0),
        sample_(profile->SampleAt(0)) {
    ClearInliningData();
  }

  bool Down() {
    if (as_functions_) {
      return UpdateFunctionIndex();
    } else {
      ++index_;
      return (index_ < sample_->length());
    }
  }

  const char* CurrentName() {
    if (as_functions_) {
      ProfileFunction* func = GetFunction();
      EXPECT(func != nullptr);
      return func->Name();
    } else {
      ProfileCode* code = GetCode();
      EXPECT(code != nullptr);
      return code->name();
    }
  }

  const char* CurrentToken() {
    if (!as_functions_) {
      return nullptr;
    }
    ProfileFunction* func = GetFunction();
    const Function& function = *(func->function());
    if (function.IsNull()) {
      // No function.
      return nullptr;
    }
    Zone* zone = Thread::Current()->zone();
    const Script& script = Script::Handle(zone, function.script());
    if (script.IsNull()) {
      // No script.
      return nullptr;
    }
    ProfileFunctionSourcePosition pfsp(TokenPosition::kNoSource);
    if (!func->GetSinglePosition(&pfsp)) {
      // Not exactly one source position.
      return nullptr;
    }

    const TokenPosition& token_pos = pfsp.token_pos();
    intptr_t line, column;
    if (script.GetTokenLocation(token_pos, &line, &column)) {
      const intptr_t token_len = script.GetTokenLength(token_pos);
      const auto& str = String::Handle(
          zone, script.GetSnippet(line, column, line, column + token_len));
      if (!str.IsNull()) return str.ToCString();
    }
    // Couldn't get line/number information.
    return nullptr;
  }

  intptr_t CurrentInclusiveTicks() {
    if (as_functions_) {
      ProfileFunction* func = GetFunction();
      EXPECT(func != nullptr);
      return func->inclusive_ticks();
    } else {
      ProfileCode* code = GetCode();
      ASSERT(code != nullptr);
      return code->inclusive_ticks();
    }
  }

  intptr_t CurrentExclusiveTicks() {
    if (as_functions_) {
      ProfileFunction* func = GetFunction();
      EXPECT(func != nullptr);
      return func->exclusive_ticks();
    } else {
      ProfileCode* code = GetCode();
      ASSERT(code != nullptr);
      return code->exclusive_ticks();
    }
  }

  const char* VMTagName() { return VMTag::TagName(sample_->vm_tag()); }

 private:
  ProfileCode* GetCode() {
    uword pc = sample_->At(index_);
    int64_t timestamp = sample_->timestamp();
    return profile_->GetCodeFromPC(pc, timestamp);
  }

  static constexpr intptr_t kInvalidInlinedIndex = -1;

  bool UpdateFunctionIndex() {
    if (inlined_index_ != kInvalidInlinedIndex) {
      if (inlined_index_ - 1 >= 0) {
        --inlined_index_;
        return true;
      }
      ClearInliningData();
    }
    ++index_;
    return (index_ < sample_->length());
  }

  void ClearInliningData() {
    inlined_index_ = kInvalidInlinedIndex;
    inlined_functions_ = nullptr;
    inlined_token_positions_ = nullptr;
  }

  ProfileFunction* GetFunction() {
    // Check to see if we're currently processing inlined functions. If so,
    // return the next inlined function.
    ProfileFunction* function = GetInlinedFunction();
    if (function != nullptr) {
      return function;
    }

    const uword pc = sample_->At(index_);
    ProfileCode* profile_code =
        profile_->GetCodeFromPC(pc, sample_->timestamp());
    ASSERT(profile_code != nullptr);
    function = profile_code->function();
    ASSERT(function != nullptr);

    TokenPosition token_position = TokenPosition::kNoSource;
    Code& code = Code::ZoneHandle();
    if (profile_code->code().IsCode()) {
      code ^= profile_code->code().ptr();
      inlined_functions_cache_.Get(pc, code, sample_, index_,
                                   &inlined_functions_,
                                   &inlined_token_positions_, &token_position);
    }

    if (code.IsNull() || (inlined_functions_ == nullptr) ||
        (inlined_functions_->length() <= 1)) {
      ClearInliningData();
      // No inlined functions.
      return function;
    }

    ASSERT(code.is_optimized());
    inlined_index_ = inlined_functions_->length() - 1;
    function = GetInlinedFunction();
    ASSERT(function != nullptr);
    return function;
  }

  ProfileFunction* GetInlinedFunction() {
    if ((inlined_index_ != kInvalidInlinedIndex) &&
        (inlined_index_ < inlined_functions_->length())) {
      return profile_->FindFunction(*(*inlined_functions_)[inlined_index_]);
    }
    return nullptr;
  }

  Profile* profile_;
  bool as_functions_;
  intptr_t index_;
  ProcessedSample* sample_;
  ProfileCodeInlinedFunctionsCache inlined_functions_cache_;
  GrowableArray<const Function*>* inlined_functions_;
  GrowableArray<TokenPosition>* inlined_token_positions_;
  intptr_t inlined_index_;
};

ISOLATE_UNIT_TEST_CASE(Profiler_TrivialRecordAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "}\n"
      "class B {\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  return B.boo();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const int64_t before_allocations_micros = Dart_TimelineGetMicros();
  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());
  class_a.SetTraceAllocation(true);

  Invoke(root_library, "main");

  const int64_t after_allocations_micros = Dart_TimelineGetMicros();
  const int64_t allocation_extent_micros =
      after_allocations_micros - before_allocations_micros;
  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    // Filter for the class in the time range.
    AllocationFilter filter(isolate->main_port(), class_a.id(),
                            before_allocations_micros,
                            allocation_extent_micros);
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have 1 allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    // Move down from the root.
    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  // Query with a time filter where no allocations occurred.
  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id(),
                            Dart_TimelineGetMicros(), 16000);
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples because none occurred within
    // the specified time range.
    EXPECT_EQ(0, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_NullSampleBuffer) {
  Isolate* isolate = thread->isolate();

  SampleFilter filter(isolate->main_port(), Thread::kMutatorTask, -1, -1);
  Profile profile;
  profile.Build(thread, isolate, &filter, nullptr);

  EXPECT_EQ(0, profile.sample_count());
}

ISOLATE_UNIT_TEST_CASE(Profiler_ToggleRecordAllocation) {
  Dart_StartProfiling();

  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "}\n"
      "class B {\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  return B.boo();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  // Turn off allocation tracing for A.
  class_a.SetTraceAllocation(false);

  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_CodeTicks) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "}\n"
      "class B {\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  return B.boo();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate three times.
  Invoke(root_library, "main");
  Invoke(root_library, "main");
  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have three allocation samples.
    EXPECT_EQ(3, profile.sample_count());
    ProfileStackWalker walker(&profile);

    // Move down from the root.
    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(3, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName());
    EXPECT_EQ(3, walker.CurrentInclusiveTicks());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName());
    EXPECT_EQ(3, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT(!walker.Down());
  }
}
ISOLATE_UNIT_TEST_CASE(Profiler_FunctionTicks) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "}\n"
      "class B {\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  return B.boo();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate three times.
  Invoke(root_library, "main");
  Invoke(root_library, "main");
  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have three allocation samples.
    EXPECT_EQ(3, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(3, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
    }
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(3, walker.CurrentInclusiveTicks());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(3, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_IntrinsicAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript = "double foo(double a, double b) => a + b;";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Class& double_class =
      Class::Handle(isolate->group()->object_store()->double_class());
  EXPECT(!double_class.IsNull());

  Dart_Handle args[2];
  {
    TransitionVMToNative transition(thread);
    args[0] = Dart_NewDouble(1.0);
    args[1] = Dart_NewDouble(2.0);
  }

  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), double_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  double_class.SetTraceAllocation(true);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), double_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    if (FLAG_interpreter) {
      // No intrinsic in the bytecode version.
      EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    } else {
      EXPECT_STREQ("Double_add", walker.VMTagName());
      EXPECT_STREQ("[Unoptimized] double._add", walker.CurrentName());
      EXPECT(walker.Down());
      EXPECT_STREQ("[Unoptimized] double.+", walker.CurrentName());
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  double_class.SetTraceAllocation(false);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), double_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_ArrayAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "List foo() => List.filled(4, null);\n"
      "List bar() => List.filled(0, null, growable: true);\n";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Class& array_class =
      Class::Handle(isolate->group()->object_store()->array_class());
  EXPECT(!array_class.IsNull());

  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), array_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  array_class.SetTraceAllocation(true);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), array_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("DRT_AllocateArray", walker.VMTagName());
    EXPECT_STREQ("[Stub] AllocateArray", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("[Unoptimized] new _List", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  array_class.SetTraceAllocation(false);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), array_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }

  // Clear the samples.
  ProfilerService::ClearSamples();

  // Compile bar (many List objects allocated).
  Invoke(root_library, "bar");

  // Enable again.
  array_class.SetTraceAllocation(true);

  // Run bar.
  Invoke(root_library, "bar");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), array_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples, since empty
    // growable lists use a shared backing.
    EXPECT_EQ(0, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_ContextAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "var msg1 = 'a';\n"
      "foo() {\n"
      "  var msg = msg1 + msg1;\n"
      "  return (x) { return '$msg + $msg'; }(msg);\n"
      "}\n";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Class& context_class =
      Class::Handle(IsolateGroup::Current()->class_table()->At(kContextCid));
  EXPECT(!context_class.IsNull());

  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), context_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  context_class.SetTraceAllocation(true);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), context_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("DRT_AllocateContext", walker.VMTagName());
    if (!FLAG_interpreter) {
      EXPECT_STREQ("[Stub] AllocateContext", walker.CurrentName());
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  context_class.SetTraceAllocation(false);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), context_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_ClosureAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "var msg1 = 'a';\n"
      "\n"
      "foo() {\n"
      "  var msg = msg1 + msg1;\n"
      "  var msg2 = msg + msg;\n"
      "  return (x, y, z, w) { return '$x + $y + $z'; }(msg, msg2, msg, msg);\n"
      "}\n"
      "bar() {\n"
      "  var msg = msg1 + msg1;\n"
      "  var msg2 = msg + msg;\n"
      "  return (x, y) { return '$x + $y'; }(msg, msg2);\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Class& closure_class =
      Class::Handle(IsolateGroup::Current()->object_store()->closure_class());
  EXPECT(!closure_class.IsNull());
  closure_class.SetTraceAllocation(true);

  // Invoke "foo" which during compilation, triggers a closure allocation.
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), closure_class.id());
    filter.set_enable_vm_ticks(true);
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_SUBSTRING("DRT_AllocateClosure", walker.VMTagName());
    if (!FLAG_interpreter) {
      EXPECT_STREQ("[Stub] AllocateClosure1", walker.CurrentName());
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  // Disable allocation tracing for Closure.
  closure_class.SetTraceAllocation(false);

  // Invoke "bar" which during compilation, triggers a closure allocation.
  Invoke(root_library, "bar");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), closure_class.id());
    filter.set_enable_vm_ticks(true);
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_TypedArrayAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "import 'dart:typed_data';\n"
      "List foo() => new Float32List(4);\n";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Library& typed_data_library =
      Library::Handle(isolate->group()->object_store()->typed_data_library());

  const Class& float32_list_class =
      Class::Handle(GetClass(typed_data_library, "_Float32List"));
  EXPECT(!float32_list_class.IsNull());

  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), float32_list_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  float32_list_class.SetTraceAllocation(true);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), float32_list_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("DRT_AllocateTypedData", walker.VMTagName());
    EXPECT_STREQ("[Stub] AllocateFloat32Array", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("[Unoptimized] new Float32List", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  float32_list_class.SetTraceAllocation(false);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), float32_list_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }

  float32_list_class.SetTraceAllocation(true);
  Invoke(root_library, "foo");

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), float32_list_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should now have two allocation samples.
    EXPECT_EQ(2, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_StringAllocation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript = "String foo(String a, String b) => a + b;";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Class& one_byte_string_class =
      Class::Handle(isolate->group()->object_store()->one_byte_string_class());
  EXPECT(!one_byte_string_class.IsNull());

  Dart_Handle args[2];
  {
    TransitionVMToNative transition(thread);
    args[0] = NewString("a");
    args[1] = NewString("b");
  }

  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  one_byte_string_class.SetTraceAllocation(true);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("String_concat", walker.VMTagName());
    EXPECT_STREQ("[Unoptimized] _StringBase.+", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  one_byte_string_class.SetTraceAllocation(false);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }

  one_byte_string_class.SetTraceAllocation(true);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should now have two allocation samples.
    EXPECT_EQ(2, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_StringInterpolation) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript = "String foo(String a, String b) => '$a | $b';";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));
  Isolate* isolate = thread->isolate();

  const Class& one_byte_string_class =
      Class::Handle(isolate->group()->object_store()->one_byte_string_class());
  EXPECT(!one_byte_string_class.IsNull());

  Dart_Handle args[2];
  {
    TransitionVMToNative transition(thread);
    args[0] = NewString("a");
    args[1] = NewString("b");
  }

  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  one_byte_string_class.SetTraceAllocation(true);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("Internal_allocateOneByteString", walker.VMTagName());
    EXPECT(walker.Down());
    EXPECT_STREQ("[Unoptimized] String._allocate", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("[Unoptimized] String._concatAll", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("[Unoptimized] _StringBase._interpolate",
                 walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("foo", walker.CurrentName());
    EXPECT(!walker.Down());
  }

  one_byte_string_class.SetTraceAllocation(false);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should still only have one allocation sample.
    EXPECT_EQ(1, profile.sample_count());
  }

  one_byte_string_class.SetTraceAllocation(true);
  Invoke(root_library, "foo", 2, &args[0]);

  {
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should now have two allocation samples.
    EXPECT_EQ(2, profile.sample_count());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_FunctionInline) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 30000);

  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "}\n"
      "class B {\n"
      "  static choo(bool alloc) {\n"
      "    if (alloc) return new A();\n"
      "    return alloc && alloc && !alloc;\n"
      "  }\n"
      "  static foo(bool alloc) {\n"
      "    choo(alloc);\n"
      "  }\n"
      "  static boo(bool alloc) {\n"
      "    for (var i = 0; i < 50000; i++) {\n"
      "      foo(alloc);\n"
      "    }\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  B.boo(false);\n"
      "}\n"
      "mainA() {\n"
      "  B.boo(true);\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  // Compile "main".
  Invoke(root_library, "main");
  // Compile "mainA".
  Invoke(root_library, "mainA");
  // At this point B.boo should be optimized and inlined B.foo and B.choo.

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate 50,000 instances of A.
  Invoke(root_library, "mainA");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have 50,000 allocation samples.
    EXPECT_EQ(50000, profile.sample_count());
    {
      ProfileStackWalker walker(&profile);
      // We have two code objects: mainA and B.boo.
      EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(50000, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
      EXPECT_STREQ("[Optimized] B.boo", walker.CurrentName());
      EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
      EXPECT(walker.Down());
      EXPECT_STREQ("[Unoptimized] mainA", walker.CurrentName());
      EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
      EXPECT_EQ(0, walker.CurrentExclusiveTicks());
      EXPECT(!walker.Down());
    }
    {
      ProfileStackWalker walker(&profile, true);
      // Inline expansion should show us the complete call chain:
      // mainA -> B.boo -> B.foo -> B.choo.
      EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(50000, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
      EXPECT_STREQ("B.choo", walker.CurrentName());
      EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
      EXPECT(walker.Down());
      EXPECT_STREQ("B.foo", walker.CurrentName());
      EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
      EXPECT_EQ(0, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
      EXPECT_STREQ("B.boo", walker.CurrentName());
      EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
      EXPECT_EQ(0, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
      EXPECT_STREQ("mainA", walker.CurrentName());
      EXPECT_EQ(50000, walker.CurrentInclusiveTicks());
      EXPECT_EQ(0, walker.CurrentExclusiveTicks());
      EXPECT(!walker.Down());
    }
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_InliningIntervalBoundary) {
  // The PC of frames below the top frame is a call's return address,
  // which can belong to a different inlining interval than the call.
  // This test checks the profiler service takes this into account; see
  // ProfileBuilder::ProcessFrame.

  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 30000);

  const char* kScript =
      "class A {\n"
      "}\n"
      "bool alloc = false;"
      "maybeAlloc() {\n"
      "  try {\n"
      "    if (alloc) new A();\n"
      "  } catch (e) {\n"
      "  }\n"
      "}\n"
      "right() => maybeAlloc();\n"
      "doNothing() {\n"
      "  try {\n"
      "  } catch (e) {\n"
      "  }\n"
      "}\n"
      "wrong() => doNothing();\n"
      "a() {\n"
      "  try {\n"
      "    right();\n"
      "    wrong();\n"
      "  } catch (e) {\n"
      "  }\n"
      "}\n"
      "mainNoAlloc() {\n"
      "  for (var i = 0; i < 20000; i++) {\n"
      "    a();\n"
      "  }\n"
      "}\n"
      "mainAlloc() {\n"
      "  alloc = true;\n"
      "  a();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  // Compile and optimize.
  Invoke(root_library, "mainNoAlloc");
  Invoke(root_library, "mainAlloc");

  // At this point a should be optimized and have inlined both right and wrong,
  // but not maybeAllocate or doNothing.
  Function& func = Function::Handle();
  func = GetFunction(root_library, "a");
  EXPECT(!func.is_inlinable());
  EXPECT(func.HasOptimizedCode());
  func = GetFunction(root_library, "right");
  EXPECT(func.is_inlinable());
  func = GetFunction(root_library, "wrong");
  EXPECT(func.is_inlinable());
  func = GetFunction(root_library, "doNothing");
  EXPECT(!func.is_inlinable());
  func = GetFunction(root_library, "maybeAlloc");
  EXPECT(!func.is_inlinable());

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have no allocation samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  Invoke(root_library, "mainAlloc");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    // Inline expansion should show us the complete call chain:
    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
    EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
    EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
    EXPECT(walker.Down());
    EXPECT_STREQ("maybeAlloc", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("right", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("a", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_STREQ("mainAlloc", walker.CurrentName());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_ChainedSamples) {
  Dart_StartProfiling();
  MaxProfileDepthScope mpds(32);
  DisableNativeProfileScope dnps;

  // Each sample holds 8 stack frames.
  // This chain is 20 stack frames deep.
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "}\n"
      "class B {\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "go() => init();\n"
      "init() => secondInit();\n"
      "secondInit() => apple();\n"
      "apple() => banana();\n"
      "banana() => cantaloupe();\n"
      "cantaloupe() => dog();\n"
      "dog() => elephant();\n"
      "elephant() => fred();\n"
      "fred() => granola();\n"
      "granola() => haystack();\n"
      "haystack() => ice();\n"
      "ice() => jeep();\n"
      "jeep() => kindle();\n"
      "kindle() => lemon();\n"
      "lemon() => mayo();\n"
      "mayo() => napkin();\n"
      "napkin() => orange();\n"
      "orange() => B.boo();\n"
      "main() {\n"
      "  return go();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());
  class_a.SetTraceAllocation(true);

  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have 1 allocation sample.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT(walker.Down());
    }
    EXPECT_UNOPTIMIZED_OR_BYTECODE("B.boo", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("orange", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("napkin", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("mayo", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("lemon", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("kindle", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("jeep", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("ice", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("haystack", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("granola", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("fred", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("elephant", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("dog", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("cantaloupe", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("banana", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("apple", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("secondInit", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("init", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("go", walker.CurrentName());
    EXPECT(walker.Down());
    EXPECT_UNOPTIMIZED_OR_BYTECODE("main", walker.CurrentName());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePosition) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "  @pragma('vm:never-inline') A() { }\n"
      "}\n"
      "class B {\n"
      "  @pragma('vm:prefer-inline')\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  B.boo();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  Invoke(root_library, "main");

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate one time.
  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation samples.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(1, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
    }
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_STREQ("A", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("boo", walker.CurrentToken());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_BasicSourcePositionOptimized) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "  @pragma('vm:never-inline') A() { }\n"
      "}\n"
      "class B {\n"
      "  @pragma('vm:prefer-inline')\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  B.boo();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  const Function& main = Function::Handle(GetFunction(root_library, "main"));
  EXPECT(!main.IsNull());

  // Optimize quickly.
  SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 5);
  // Warm up function.
  while (true) {
    Invoke(root_library, "main");
    const Code& code = Code::Handle(main.CurrentCode());
    if (code.is_optimized()) {
      // Warmed up.
      break;
    }
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate one time.
  Invoke(root_library, "main");

  // Still optimized.
  const Code& code = Code::Handle(main.CurrentCode());
  EXPECT(code.is_optimized());

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation samples.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    // Move down from the root.
    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
    EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
    EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
    EXPECT_EQ(1, walker.CurrentExclusiveTicks());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_STREQ("A", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("boo", walker.CurrentToken());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_SourcePosition) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "  @pragma('vm:never-inline') A() { }\n"
      "}\n"
      "class B {\n"
      "  @pragma('vm:never-inline')\n"
      "  static oats() {\n"
      "    return boo();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline')\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "class C {\n"
      "  @pragma('vm:never-inline') bacon() {\n"
      "    return fox();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline') fox() {\n"
      "    return B.oats();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  new C()..bacon();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  Invoke(root_library, "main");

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate one time.
  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation samples.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(1, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
    }
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_STREQ("A", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.oats", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("boo", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.fox", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("oats", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.bacon", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("fox", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("bacon", walker.CurrentToken());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_SourcePositionOptimized) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "  @pragma('vm:never-inline') A() { }\n"
      "}\n"
      "class B {\n"
      "  @pragma('vm:never-inline')\n"
      "  static oats() {\n"
      "    return boo();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline')\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "class C {\n"
      "  @pragma('vm:never-inline') bacon() {\n"
      "    return fox();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline') fox() {\n"
      "    return B.oats();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  new C()..bacon();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  const Function& main = Function::Handle(GetFunction(root_library, "main"));
  EXPECT(!main.IsNull());

  // Optimize quickly.
  SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 5);
  // Warm up function.
  while (true) {
    Invoke(root_library, "main");
    const Code& code = Code::Handle(main.CurrentCode());
    if (code.is_optimized()) {
      // Warmed up.
      break;
    }
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate one time.
  Invoke(root_library, "main");

  // Still optimized.
  const Code& code = Code::Handle(main.CurrentCode());
  EXPECT(code.is_optimized());

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation samples.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
    EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
    EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
    EXPECT_EQ(1, walker.CurrentExclusiveTicks());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_STREQ("A", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.oats", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("boo", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.fox", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("oats", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.bacon", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("fox", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("bacon", walker.CurrentToken());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePosition) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;
  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "  @pragma('vm:never-inline') A() { }\n"
      "}\n"
      "class B {\n"
      "  @pragma('vm:never-inline')\n"
      "  static oats() {\n"
      "    return boo();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline')\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "class C {\n"
      "  @pragma('vm:never-inline') bacon() {\n"
      "    return this + this;\n"
      "  }\n"
      "  @pragma('vm:prefer-inline') operator+(C other) {\n"
      "    return fox();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline') fox() {\n"
      "    return B.oats();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  new C()..bacon();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  Invoke(root_library, "main");

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate one time.
  Invoke(root_library, "main");

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation samples.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
    if (!FLAG_interpreter) {
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
      EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
      EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
      EXPECT_EQ(1, walker.CurrentExclusiveTicks());
      EXPECT(walker.Down());
    }
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_STREQ("A", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.oats", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("boo", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.fox", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("oats", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.+", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("fox", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.bacon", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("+", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("bacon", walker.CurrentToken());
    EXPECT(!walker.Down());
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_BinaryOperatorSourcePositionOptimized) {
  Dart_StartProfiling();
  DisableNativeProfileScope dnps;
  DisableBackgroundCompilationScope dbcs;

  const char* kScript =
      "class A {\n"
      "  var a;\n"
      "  var b;\n"
      "  @pragma('vm:never-inline') A() { }\n"
      "}\n"
      "class B {\n"
      "  @pragma('vm:never-inline')\n"
      "  static oats() {\n"
      "    return boo();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline')\n"
      "  static boo() {\n"
      "    return new A();\n"
      "  }\n"
      "}\n"
      "class C {\n"
      "  @pragma('vm:never-inline') bacon() {\n"
      "    return this + this;\n"
      "  }\n"
      "  @pragma('vm:prefer-inline') operator+(C other) {\n"
      "    return fox();\n"
      "  }\n"
      "  @pragma('vm:prefer-inline') fox() {\n"
      "    return B.oats();\n"
      "  }\n"
      "}\n"
      "main() {\n"
      "  new C()..bacon();\n"
      "}\n";

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  const Class& class_a = Class::Handle(GetClass(root_library, "A"));
  EXPECT(!class_a.IsNull());

  const Function& main = Function::Handle(GetFunction(root_library, "main"));
  EXPECT(!main.IsNull());

  // Optimize quickly.
  SetFlagScope<int> sfs(&FLAG_optimization_counter_threshold, 5);
  // Warm up function.
  while (true) {
    Invoke(root_library, "main");
    const Code& code = Code::Handle(main.CurrentCode());
    if (code.is_optimized()) {
      // Warmed up.
      break;
    }
  }

  // Turn on allocation tracing for A.
  class_a.SetTraceAllocation(true);

  // Allocate one time.
  Invoke(root_library, "main");

  // Still optimized.
  const Code& code = Code::Handle(main.CurrentCode());
  EXPECT(code.is_optimized());

  {
    Thread* thread = Thread::Current();
    Isolate* isolate = thread->isolate();
    StackZone zone(thread);
    Profile profile;
    AllocationFilter filter(isolate->main_port(), class_a.id());
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We should have one allocation samples.
    EXPECT_EQ(1, profile.sample_count());
    ProfileStackWalker walker(&profile, true);

    EXPECT_STREQ("DRT_AllocateObject", walker.VMTagName());
#if defined(TARGET_ARCH_IA32)  // Alloc. stub not impl. for ia32.
    EXPECT_STREQ("[Stub] Allocate A", walker.CurrentName());
#else
    EXPECT_STREQ("[Stub] AllocateObjectSlow", walker.CurrentName());
#endif
    EXPECT_EQ(1, walker.CurrentExclusiveTicks());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.boo", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_STREQ("A", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("B.oats", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("boo", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.fox", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("oats", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.+", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("fox", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("C.bacon", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("+", walker.CurrentToken());
    EXPECT(walker.Down());
    EXPECT_STREQ("main", walker.CurrentName());
    EXPECT_EQ(1, walker.CurrentInclusiveTicks());
    EXPECT_EQ(0, walker.CurrentExclusiveTicks());
    EXPECT_STREQ("bacon", walker.CurrentToken());
    EXPECT(!walker.Down());
  }
}

static void InsertFakeSample(uword* pc_offsets) {
  Isolate* isolate = Isolate::Current();
  ASSERT(Profiler::sample_block_buffer() != nullptr);
  Sample* sample = Profiler::sample_block_buffer()->ReserveCPUSample(isolate);
  ASSERT(sample != nullptr);
  sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(),
               OSThread::Current()->trace_id());
  sample->set_thread_task(Thread::kMutatorTask);

  intptr_t i = 0;
  while (pc_offsets[i] != 0) {
    // When we collect a real stack trace, all PCs collected aside from the
    // executing one (i == 0) are actually return addresses. Return addresses
    // are one byte beyond the call instruction that is executing. The profiler
    // accounts for this and subtracts one from these addresses when querying
    // inline and token position ranges. To be consistent with real stack
    // traces, we add one byte to all PCs except the executing one.
    // See OffsetForPC in profiler_service.cc for more context.
    const intptr_t return_address_offset = i > 0 ? 1 : 0;
    sample->SetAt(i, pc_offsets[i] + return_address_offset);
    i++;
  }
  sample->SetAt(i, 0);
}

static uword FindPCForTokenPosition(const Code& code, TokenPosition tp) {
  GrowableArray<const Function*> functions;
  GrowableArray<TokenPosition> token_positions;
  for (uword pc_offset = 0; pc_offset < code.Size(); pc_offset++) {
    code.GetInlinedFunctionsAtInstruction(pc_offset, &functions,
                                          &token_positions);
    if (token_positions[0] == tp) {
      return code.PayloadStart() + pc_offset;
    }
  }

  return 0;
}

ISOLATE_UNIT_TEST_CASE(Profiler_GetSourceReport) {
  Dart_StartProfiling();
  const char* kScript =
      "int doWork(i) => i * i;\n"
      "int main() {\n"
      "  int sum = 0;\n"
      "  for (int i = 0; i < 100; i++) {\n"
      "     sum += doWork(i);\n"
      "  }\n"
      "  return sum;\n"
      "}\n";

  // Token position of * in `i * i`.
  const TokenPosition squarePosition = TokenPosition::Deserialize(19);

  // Token position of the call to `doWork`.
  const TokenPosition callPosition = TokenPosition::Deserialize(95);

  DisableNativeProfileScope dnps;
  // Disable profiling for this thread.
  DisableThreadInterruptsScope dtis(Thread::Current());

  DisableBackgroundCompilationScope dbcs;

  SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer();
  ASSERT(sample_block_buffer != nullptr);

  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  // Invoke main so that it gets compiled.
  Invoke(root_library, "main");

  {
    // Clear the profile for this isolate.
    ClearProfileVisitor cpv(Isolate::Current());
    sample_block_buffer->VisitSamples(&cpv);
  }

  // Query the code object for main and determine the PC at some token
  // positions.
  const Function& main = Function::Handle(GetFunction(root_library, "main"));
  EXPECT(!main.IsNull());

  const Function& do_work =
      Function::Handle(GetFunction(root_library, "doWork"));
  EXPECT(!do_work.IsNull());

  const Script& script = Script::Handle(main.script());
  EXPECT(!script.IsNull());

  const Code& main_code = Code::Handle(main.CurrentCode());
  EXPECT(!main_code.IsNull());

  const Code& do_work_code = Code::Handle(do_work.CurrentCode());
  EXPECT(!do_work_code.IsNull());

  // Dump code source map.
  do_work_code.DumpSourcePositions();
  main_code.DumpSourcePositions();

  // Look up some source token position's pc.
  uword squarePositionPc = FindPCForTokenPosition(do_work_code, squarePosition);
  EXPECT(squarePositionPc != 0);

  uword callPositionPc = FindPCForTokenPosition(main_code, callPosition);
  EXPECT(callPositionPc != 0);

  // Look up some classifying token position's pc.
  uword controlFlowPc =
      FindPCForTokenPosition(do_work_code, TokenPosition::kControlFlow);
  EXPECT(controlFlowPc != 0);

  // Insert fake samples.

  // Sample 1:
  // squarePositionPc exclusive.
  // callPositionPc inclusive.
  uword sample1[] = {squarePositionPc,  // doWork.
                     callPositionPc,    // main.
                     0};

  // Sample 2:
  // squarePositionPc exclusive.
  uword sample2[] = {
      squarePositionPc,  // doWork.
      0,
  };

  // Sample 3:
  // controlFlowPc exclusive.
  // callPositionPc inclusive.
  uword sample3[] = {controlFlowPc,   // doWork.
                     callPositionPc,  // main.
                     0};

  InsertFakeSample(&sample1[0]);
  InsertFakeSample(&sample2[0]);
  InsertFakeSample(&sample3[0]);

  // Generate source report for main.
  JSONStream js;
  {
    SourceReport sourceReport(SourceReport::kProfile);
    sourceReport.PrintJSON(&js, script, do_work.token_pos(),
                           main.end_token_pos());
  }

  // Verify positions in do_work.
  EXPECT_SUBSTRING("\"positions\":[\"ControlFlow\",19]", js.ToCString());
  // Verify exclusive ticks in do_work.
  EXPECT_SUBSTRING("\"exclusiveTicks\":[1,2]", js.ToCString());
  // Verify inclusive ticks in do_work.
  EXPECT_SUBSTRING("\"inclusiveTicks\":[1,2]", js.ToCString());

  // Verify positions in main.
  EXPECT_SUBSTRING("\"positions\":[95]", js.ToCString());
  // Verify exclusive ticks in main.
  EXPECT_SUBSTRING("\"exclusiveTicks\":[0]", js.ToCString());
  // Verify inclusive ticks in main.
  EXPECT_SUBSTRING("\"inclusiveTicks\":[2]", js.ToCString());
}

ISOLATE_UNIT_TEST_CASE(Profiler_ProfileCodeTableTest) {
  Zone* Z = Thread::Current()->zone();

  ProfileCodeTable* table = new (Z) ProfileCodeTable();
  EXPECT_EQ(table->length(), 0);
  EXPECT_EQ(table->FindCodeForPC(42), static_cast<ProfileCode*>(nullptr));

  int64_t timestamp = 0;
  const AbstractCode null_code(Code::null());

  ProfileCode* code1 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 50, 60, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code1), 0);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(55), code1);
  EXPECT_EQ(table->FindCodeForPC(59), code1);
  EXPECT_EQ(table->FindCodeForPC(60), static_cast<ProfileCode*>(nullptr));

  // Insert below all.
  ProfileCode* code2 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 10, 20, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code2), 0);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(10), code2);
  EXPECT_EQ(table->FindCodeForPC(19), code2);
  EXPECT_EQ(table->FindCodeForPC(20), static_cast<ProfileCode*>(nullptr));

  // Insert above all.
  ProfileCode* code3 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 80, 90, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code3), 2);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(10), code2);
  EXPECT_EQ(table->FindCodeForPC(80), code3);
  EXPECT_EQ(table->FindCodeForPC(89), code3);
  EXPECT_EQ(table->FindCodeForPC(90), static_cast<ProfileCode*>(nullptr));

  // Insert between.
  ProfileCode* code4 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 65, 75, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code4), 2);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(10), code2);
  EXPECT_EQ(table->FindCodeForPC(80), code3);
  EXPECT_EQ(table->FindCodeForPC(65), code4);
  EXPECT_EQ(table->FindCodeForPC(74), code4);
  EXPECT_EQ(table->FindCodeForPC(75), static_cast<ProfileCode*>(nullptr));

  // Insert overlapping left.
  ProfileCode* code5 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 15, 25, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code5), 0);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(10), code2);
  EXPECT_EQ(table->FindCodeForPC(80), code3);
  EXPECT_EQ(table->FindCodeForPC(65), code4);
  EXPECT_EQ(table->FindCodeForPC(15), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(24), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(25), static_cast<ProfileCode*>(nullptr));

  // Insert overlapping right.
  ProfileCode* code6 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 45, 55, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code6), 1);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(10), code2);
  EXPECT_EQ(table->FindCodeForPC(80), code3);
  EXPECT_EQ(table->FindCodeForPC(65), code4);
  EXPECT_EQ(table->FindCodeForPC(15), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(24), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(45), code1);  // Merged right.
  EXPECT_EQ(table->FindCodeForPC(54), code1);  // Merged right.
  EXPECT_EQ(table->FindCodeForPC(55), code1);

  // Insert overlapping both.
  ProfileCode* code7 = new (Z)
      ProfileCode(ProfileCode::kNativeCode, 20, 50, timestamp, null_code);
  EXPECT_EQ(table->InsertCode(code7), 0);
  EXPECT_EQ(table->FindCodeForPC(0), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(100), static_cast<ProfileCode*>(nullptr));
  EXPECT_EQ(table->FindCodeForPC(50), code1);
  EXPECT_EQ(table->FindCodeForPC(10), code2);
  EXPECT_EQ(table->FindCodeForPC(80), code3);
  EXPECT_EQ(table->FindCodeForPC(65), code4);
  EXPECT_EQ(table->FindCodeForPC(15), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(24), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(45), code1);  // Merged right.
  EXPECT_EQ(table->FindCodeForPC(54), code1);  // Merged right.
  EXPECT_EQ(table->FindCodeForPC(20), code2);  // Merged left.
  EXPECT_EQ(table->FindCodeForPC(49), code1);  // Truncated.
  EXPECT_EQ(table->FindCodeForPC(50), code1);
}

// Try to hit any races in related to setting TLS and Isolate::mutator_thread_.
// https://github.com/flutter/flutter/issues/134548
ISOLATE_UNIT_TEST_CASE(Profiler_EnterExitIsolate) {
  Profiler::SetConfig({
      .enabled = true,
      .period_us = 50,
  });

  const char* kScript = "main() => null;\n";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  Isolate* isolate = Isolate::Current();
  for (intptr_t i = 0; i < 100000; i++) {
    Thread::ExitIsolate();
    Thread::EnterIsolate(isolate);
    Invoke(root_library, "main");
  }
}

// Poke a lot at OSThread::thread_interrupt_disabled_, which will be read by the
// sampling thread.
// https://github.com/dart-lang/sdk/issues/62332
ISOLATE_UNIT_TEST_CASE(Profiler_ThreadEnableDisableProfiler) {
  Profiler::SetConfig({
      .enabled = true,
      .period_us = 50,
  });

  for (intptr_t i = 0; i < 100000; i++) {
    Dart_ThreadDisableProfiling();
    Dart_ThreadEnableProfiling();
  }
}

ISOLATE_UNIT_TEST_CASE(Profiler_UpdateRunningState) {
  Isolate* isolate = Isolate::Current();
  SampleFilter filter(isolate->main_port(), Thread::kMutatorTask, -1, -1);

  Profiler::SetConfig({
      .enabled = true,
      .period_us = 50,
  });

  const char* kScript = R"(
void main() {
  final sw = Stopwatch()..start();
  while (sw.elapsedMilliseconds < 1000) {
  }
})";
  const Library& root_library = Library::Handle(LoadTestScript(kScript));

  // Test that profiler collects some samples while main is running if
  // it is enabled.
  Invoke(root_library, "main");
  {
    Profile profile;
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We expect to have collected at least some samples.
    EXPECT_LT(0, profile.sample_count());
  }

  // Switch profiler off and clear the sample block buffer to discard all
  // collected samples.
  Dart_StopProfiling();
  delete Profiler::sample_block_buffer();
  Profiler::set_sample_block_buffer(new SampleBlockBuffer());

  // Test that no samples are collected if profiler is disabled.
  Invoke(root_library, "main");
  {
    Profile profile;
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());
    // We expect to have collected no samples.
    EXPECT_EQ(0, profile.sample_count());
  }

  // Enable profiler again.
  Dart_StartProfiling();

  // Test again that samples are collected.
  Invoke(root_library, "main");
  {
    Profile profile;
    profile.Build(thread, isolate, &filter, Profiler::sample_block_buffer());

    // We expect to have collected at least some samples.
    EXPECT_LT(0, profile.sample_count());
  }
}

#endif  // !PRODUCT

}  // namespace dart
