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