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 #endif
75 }
76
77 private:
78 v8::internal::Simulator* simulator_;
79 };
80 #endif // USE_SIMULATOR
81
82
83 class 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
SamplingTestHelper(const std::string & test_function)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
~SamplingTestHelper()106 ~SamplingTestHelper() {
107 isolate_->SetJitCodeEventHandler(v8::kJitCodeEventDefault, NULL);
108 instance_ = NULL;
109 }
110
sample()111 Sample& sample() { return sample_; }
112
FindEventEntry(const void * address)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:
CollectSample(const v8::FunctionCallbackInfo<v8::Value> & args)123 static void CollectSample(const v8::FunctionCallbackInfo<v8::Value>& args) {
124 instance_->DoCollectSample();
125 }
126
JitCodeEventHandler(const v8::JitCodeEvent * event)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.
DoCollectSample()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
DoJitCodeEventHandler(const v8::JitCodeEvent * event)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
190 SamplingTestHelper* 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.
200 static const char* test_function =
201 "function func(depth) {"
202 " if (depth == 2) CollectSample();"
203 " else return func(depth - 1);"
204 "}";
205
206
TEST(StackDepthIsConsistent)207 TEST(StackDepthIsConsistent) {
208 SamplingTestHelper helper(std::string(test_function) + "func(8);");
209 CHECK_EQ(8, helper.sample().size());
210 }
211
212
TEST(StackDepthDoesNotExceedMaxValue)213 TEST(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
TEST(StackFramesConsistent)225 TEST(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