1// Copyright 2014 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4//
5// Tests the sampling API in include/v8.h
6
7#include <map>
8#include <string>
9#include "include/v8.h"
10#include "src/simulator.h"
11#include "test/cctest/cctest.h"
12
13namespace {
14
15class Sample {
16 public:
17  enum { kFramesLimit = 255 };
18
19  Sample() {}
20
21  typedef const void* const* const_iterator;
22  const_iterator begin() const { return data_.start(); }
23  const_iterator end() const { return &data_[data_.length()]; }
24
25  int size() const { return data_.length(); }
26  v8::internal::Vector<void*>& data() { return data_; }
27
28 private:
29  v8::internal::EmbeddedVector<void*, kFramesLimit> data_;
30};
31
32
33#if defined(USE_SIMULATOR)
34class SimulatorHelper {
35 public:
36  inline bool Init(v8::Isolate* isolate) {
37    simulator_ = reinterpret_cast<v8::internal::Isolate*>(isolate)
38                     ->thread_local_top()
39                     ->simulator_;
40    // Check if there is active simulator.
41    return simulator_ != NULL;
42  }
43
44  inline void FillRegisters(v8::RegisterState* state) {
45#if V8_TARGET_ARCH_ARM
46    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
47    state->sp = reinterpret_cast<void*>(
48        simulator_->get_register(v8::internal::Simulator::sp));
49    state->fp = reinterpret_cast<void*>(
50        simulator_->get_register(v8::internal::Simulator::r11));
51#elif V8_TARGET_ARCH_ARM64
52    if (simulator_->sp() == 0 || simulator_->fp() == 0) {
53      // It's possible that the simulator is interrupted while it is updating
54      // the sp or fp register. ARM64 simulator does this in two steps:
55      // first setting it to zero and then setting it to a new value.
56      // Bailout if sp/fp doesn't contain the new value.
57      return;
58    }
59    state->pc = reinterpret_cast<void*>(simulator_->pc());
60    state->sp = reinterpret_cast<void*>(simulator_->sp());
61    state->fp = reinterpret_cast<void*>(simulator_->fp());
62#elif V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
63    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
64    state->sp = reinterpret_cast<void*>(
65        simulator_->get_register(v8::internal::Simulator::sp));
66    state->fp = reinterpret_cast<void*>(
67        simulator_->get_register(v8::internal::Simulator::fp));
68#elif V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
69    state->pc = reinterpret_cast<void*>(simulator_->get_pc());
70    state->sp = reinterpret_cast<void*>(
71        simulator_->get_register(v8::internal::Simulator::sp));
72    state->fp = reinterpret_cast<void*>(
73        simulator_->get_register(v8::internal::Simulator::fp));
74#endif
75  }
76
77 private:
78  v8::internal::Simulator* simulator_;
79};
80#endif  // USE_SIMULATOR
81
82
83class SamplingTestHelper {
84 public:
85  struct CodeEventEntry {
86    std::string name;
87    const void* code_start;
88    size_t code_len;
89  };
90  typedef std::map<const void*, CodeEventEntry> CodeEntries;
91
92  explicit SamplingTestHelper(const std::string& test_function)
93      : sample_is_taken_(false), isolate_(CcTest::isolate()) {
94    CHECK(!instance_);
95    instance_ = this;
96    v8::HandleScope scope(isolate_);
97    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
98    global->Set(v8_str("CollectSample"),
99                v8::FunctionTemplate::New(isolate_, CollectSample));
100    LocalContext env(isolate_, NULL, global);
101    isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
102                                     JitCodeEventHandler);
103    CompileRun(v8_str(test_function.c_str()));
104  }
105
106  ~SamplingTestHelper() {
107    isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
108    instance_ = NULL;
109  }
110
111  Sample& sample() { return sample_; }
112
113  const CodeEventEntry* FindEventEntry(const void* address) {
114    CodeEntries::const_iterator it = code_entries_.upper_bound(address);
115    if (it == code_entries_.begin()) return NULL;
116    const CodeEventEntry& entry = (--it)->second;
117    const void* code_end =
118        static_cast<const uint8_t*>(entry.code_start) + entry.code_len;
119    return address < code_end ? &entry : NULL;
120  }
121
122 private:
123  static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) {
124    instance_->DoCollectSample();
125  }
126
127  static void JitCodeEventHandler(const v8::JitCodeEvent* event) {
128    instance_->DoJitCodeEventHandler(event);
129  }
130
131  // The JavaScript calls this function when on full stack depth.
132  void DoCollectSample() {
133    v8::RegisterState state;
134#if defined(USE_SIMULATOR)
135    SimulatorHelper simulator_helper;
136    if (!simulator_helper.Init(isolate_)) return;
137    simulator_helper.FillRegisters(&state);
138#else
139    state.pc = NULL;
140    state.fp = &state;
141    state.sp = &state;
142#endif
143    v8::SampleInfo info;
144    isolate_->GetStackSample(state, sample_.data().start(),
145                             static_cast<size_t>(sample_.size()), &info);
146    size_t frames_count = info.frames_count;
147    CHECK_LE(frames_count, static_cast<size_t>(sample_.size()));
148    sample_.data().Truncate(static_cast<int>(frames_count));
149    sample_is_taken_ = true;
150  }
151
152  void DoJitCodeEventHandler(const v8::JitCodeEvent* event) {
153    if (sample_is_taken_) return;
154    switch (event->type) {
155      case v8::JitCodeEvent::CODE_ADDED: {
156        CodeEventEntry entry;
157        entry.name = std::string(event->name.str, event->name.len);
158        entry.code_start = event->code_start;
159        entry.code_len = event->code_len;
160        code_entries_.insert(std::make_pair(entry.code_start, entry));
161        break;
162      }
163      case v8::JitCodeEvent::CODE_MOVED: {
164        CodeEntries::iterator it = code_entries_.find(event->code_start);
165        CHECK(it != code_entries_.end());
166        code_entries_.erase(it);
167        CodeEventEntry entry;
168        entry.name = std::string(event->name.str, event->name.len);
169        entry.code_start = event->new_code_start;
170        entry.code_len = event->code_len;
171        code_entries_.insert(std::make_pair(entry.code_start, entry));
172        break;
173      }
174      case v8::JitCodeEvent::CODE_REMOVED:
175        code_entries_.erase(event->code_start);
176        break;
177      default:
178        break;
179    }
180  }
181
182  Sample sample_;
183  bool sample_is_taken_;
184  v8::Isolate* isolate_;
185  CodeEntries code_entries_;
186
187  static SamplingTestHelper* instance_;
188};
189
190SamplingTestHelper* SamplingTestHelper::instance_;
191
192}  // namespace
193
194
195// A JavaScript function which takes stack depth
196// (minimum value 2) as an argument.
197// When at the bottom of the recursion,
198// the JavaScript code calls into C++ test code,
199// waiting for the sampler to take a sample.
200static const char* test_function =
201    "function func(depth) {"
202    "  if (depth == 2) CollectSample();"
203    "  else return func(depth - 1);"
204    "}";
205
206
207TEST(StackDepthIsConsistent) {
208  SamplingTestHelper helper(std::string(test_function) + "func(8);");
209  CHECK_EQ(8, helper.sample().size());
210}
211
212
213TEST(StackDepthDoesNotExceedMaxValue) {
214  SamplingTestHelper helper(std::string(test_function) + "func(300);");
215  CHECK_EQ(Sample::kFramesLimit, helper.sample().size());
216}
217
218
219// The captured sample should have three pc values.
220// They should fall in the range where the compiled code resides.
221// The expected stack is:
222// bottom of stack [{anon script}, outer, inner] top of stack
223//                              ^      ^       ^
224// sample.stack indices         2      1       0
225TEST(StackFramesConsistent) {
226  i::FLAG_allow_natives_syntax = true;
227  const char* test_script =
228      "function test_sampler_api_inner() {"
229      "  CollectSample();"
230      "  return 0;"
231      "}"
232      "function test_sampler_api_outer() {"
233      "  return test_sampler_api_inner();"
234      "}"
235      "%NeverOptimizeFunction(test_sampler_api_inner);"
236      "%NeverOptimizeFunction(test_sampler_api_outer);"
237      "test_sampler_api_outer();";
238
239  SamplingTestHelper helper(test_script);
240  Sample& sample = helper.sample();
241  CHECK_EQ(3, sample.size());
242
243  const SamplingTestHelper::CodeEventEntry* entry;
244  entry = helper.FindEventEntry(sample.begin()[0]);
245  CHECK(entry);
246  CHECK(std::string::npos != entry->name.find("test_sampler_api_inner"));
247
248  entry = helper.FindEventEntry(sample.begin()[1]);
249  CHECK(entry);
250  CHECK(std::string::npos != entry->name.find("test_sampler_api_outer"));
251}
252