• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
13 namespace {
14 
15 class Sample {
16  public:
17   enum { kFramesLimit = 255 };
18 
Sample()19   Sample() {}
20 
21   typedef const void* const* const_iterator;
begin() const22   const_iterator begin() const { return data_.start(); }
end() const23   const_iterator end() const { return &data_[data_.length()]; }
24 
size() const25   int size() const { return data_.length(); }
data()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)
34 class SimulatorHelper {
35  public:
Init(v8::Isolate * isolate)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 
FillRegisters(v8::RegisterState * state)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 #elif V8_TARGET_ARCH_S390 || V8_TARGET_ARCH_S390X
75     state->pc = reinterpret_cast<void*>(simulator_->get_pc());
76     state->sp = reinterpret_cast<void*>(
77         simulator_->get_register(v8::internal::Simulator::sp));
78     state->fp = reinterpret_cast<void*>(
79         simulator_->get_register(v8::internal::Simulator::fp));
80 #endif
81   }
82 
83  private:
84   v8::internal::Simulator* simulator_;
85 };
86 #endif  // USE_SIMULATOR
87 
88 
89 class SamplingTestHelper {
90  public:
91   struct CodeEventEntry {
92     std::string name;
93     const void* code_start;
94     size_t code_len;
95   };
96   typedef std::map<const void*, CodeEventEntry> CodeEntries;
97 
SamplingTestHelper(const std::string & test_function)98   explicit SamplingTestHelper(const std::string& test_function)
99       : sample_is_taken_(false), isolate_(CcTest::isolate()) {
100     CHECK(!instance_);
101     instance_ = this;
102     v8::HandleScope scope(isolate_);
103     v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
104     global->Set(v8_str("CollectSample"),
105                 v8::FunctionTemplate::New(isolate_, CollectSample));
106     LocalContext env(isolate_, NULL, global);
107     isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault,
108                                      JitCodeEventHandler);
109     CompileRun(v8_str(test_function.c_str()));
110   }
111 
~SamplingTestHelper()112   ~SamplingTestHelper() {
113     isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
114     instance_ = NULL;
115   }
116 
sample()117   Sample& sample() { return sample_; }
118 
FindEventEntry(const void * address)119   const CodeEventEntry* FindEventEntry(const void* address) {
120     CodeEntries::const_iterator it = code_entries_.upper_bound(address);
121     if (it == code_entries_.begin()) return NULL;
122     const CodeEventEntry& entry = (--it)->second;
123     const void* code_end =
124         static_cast<const uint8_t*>(entry.code_start) + entry.code_len;
125     return address < code_end ? &entry : NULL;
126   }
127 
128  private:
CollectSample(const v8::FunctionCallbackInfo<v8::Value> & args)129   static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) {
130     instance_->DoCollectSample();
131   }
132 
JitCodeEventHandler(const v8::JitCodeEvent * event)133   static void JitCodeEventHandler(const v8::JitCodeEvent* event) {
134     instance_->DoJitCodeEventHandler(event);
135   }
136 
137   // The JavaScript calls this function when on full stack depth.
DoCollectSample()138   void DoCollectSample() {
139     v8::RegisterState state;
140 #if defined(USE_SIMULATOR)
141     SimulatorHelper simulator_helper;
142     if (!simulator_helper.Init(isolate_)) return;
143     simulator_helper.FillRegisters(&state);
144 #else
145     state.pc = NULL;
146     state.fp = &state;
147     state.sp = &state;
148 #endif
149     v8::SampleInfo info;
150     isolate_->GetStackSample(state, sample_.data().start(),
151                              static_cast<size_t>(sample_.size()), &info);
152     size_t frames_count = info.frames_count;
153     CHECK_LE(frames_count, static_cast<size_t>(sample_.size()));
154     sample_.data().Truncate(static_cast<int>(frames_count));
155     sample_is_taken_ = true;
156   }
157 
DoJitCodeEventHandler(const v8::JitCodeEvent * event)158   void DoJitCodeEventHandler(const v8::JitCodeEvent* event) {
159     if (sample_is_taken_) return;
160     switch (event->type) {
161       case v8::JitCodeEvent::CODE_ADDED: {
162         CodeEventEntry entry;
163         entry.name = std::string(event->name.str, event->name.len);
164         entry.code_start = event->code_start;
165         entry.code_len = event->code_len;
166         code_entries_.insert(std::make_pair(entry.code_start, entry));
167         break;
168       }
169       case v8::JitCodeEvent::CODE_MOVED: {
170         CodeEntries::iterator it = code_entries_.find(event->code_start);
171         CHECK(it != code_entries_.end());
172         code_entries_.erase(it);
173         CodeEventEntry entry;
174         entry.name = std::string(event->name.str, event->name.len);
175         entry.code_start = event->new_code_start;
176         entry.code_len = event->code_len;
177         code_entries_.insert(std::make_pair(entry.code_start, entry));
178         break;
179       }
180       case v8::JitCodeEvent::CODE_REMOVED:
181         code_entries_.erase(event->code_start);
182         break;
183       default:
184         break;
185     }
186   }
187 
188   Sample sample_;
189   bool sample_is_taken_;
190   v8::Isolate* isolate_;
191   CodeEntries code_entries_;
192 
193   static SamplingTestHelper* instance_;
194 };
195 
196 SamplingTestHelper* SamplingTestHelper::instance_;
197 
198 }  // namespace
199 
200 
201 // A JavaScript function which takes stack depth
202 // (minimum value 2) as an argument.
203 // When at the bottom of the recursion,
204 // the JavaScript code calls into C++ test code,
205 // waiting for the sampler to take a sample.
206 static const char* test_function =
207     "function func(depth) {"
208     "  if (depth == 2) CollectSample();"
209     "  else return func(depth - 1);"
210     "}";
211 
212 
TEST(StackDepthIsConsistent)213 TEST(StackDepthIsConsistent) {
214   SamplingTestHelper helper(std::string(test_function) + "func(8);");
215   CHECK_EQ(8, helper.sample().size());
216 }
217 
218 
TEST(StackDepthDoesNotExceedMaxValue)219 TEST(StackDepthDoesNotExceedMaxValue) {
220   SamplingTestHelper helper(std::string(test_function) + "func(300);");
221   CHECK_EQ(Sample::kFramesLimit, helper.sample().size());
222 }
223 
224 
225 // The captured sample should have three pc values.
226 // They should fall in the range where the compiled code resides.
227 // The expected stack is:
228 // bottom of stack [{anon script}, outer, inner] top of stack
229 //                              ^      ^       ^
230 // sample.stack indices         2      1       0
TEST(StackFramesConsistent)231 TEST(StackFramesConsistent) {
232   i::FLAG_allow_natives_syntax = true;
233   const char* test_script =
234       "function test_sampler_api_inner() {"
235       "  CollectSample();"
236       "  return 0;"
237       "}"
238       "function test_sampler_api_outer() {"
239       "  return test_sampler_api_inner();"
240       "}"
241       "%NeverOptimizeFunction(test_sampler_api_inner);"
242       "%NeverOptimizeFunction(test_sampler_api_outer);"
243       "test_sampler_api_outer();";
244 
245   SamplingTestHelper helper(test_script);
246   Sample& sample = helper.sample();
247   CHECK_EQ(3, sample.size());
248 
249   const SamplingTestHelper::CodeEventEntry* entry;
250   entry = helper.FindEventEntry(sample.begin()[0]);
251   CHECK(entry);
252   CHECK(std::string::npos != entry->name.find("test_sampler_api_inner"));
253 
254   entry = helper.FindEventEntry(sample.begin()[1]);
255   CHECK(entry);
256   CHECK(std::string::npos != entry->name.find("test_sampler_api_outer"));
257 }
258