blob: 312a443a6b9b1798e20cc5776ef72165d2f5357a [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// Copyright 2006-2009 the V8 project authors. All rights reserved.
2//
3// Tests of profiler-related functions from log.h
4
5#ifdef ENABLE_LOGGING_AND_PROFILING
6
7#include <stdlib.h>
8
9#include "v8.h"
10
11#include "codegen.h"
12#include "log.h"
13#include "top.h"
14#include "cctest.h"
15#include "disassembler.h"
16#include "register-allocator-inl.h"
17
18using v8::Function;
19using v8::Local;
20using v8::Object;
21using v8::Script;
22using v8::String;
23using v8::Value;
24
25using v8::internal::byte;
26using v8::internal::Address;
27using v8::internal::Handle;
28using v8::internal::JSFunction;
29using v8::internal::StackTracer;
30using v8::internal::TickSample;
31using v8::internal::Top;
32
33namespace i = v8::internal;
34
35
36static v8::Persistent<v8::Context> env;
37
38
39static struct {
40 TickSample* sample;
41} trace_env = { NULL };
42
43
44static void InitTraceEnv(TickSample* sample) {
45 trace_env.sample = sample;
46}
47
48
49static void DoTrace(Address fp) {
Leon Clarked91b9f72010-01-27 17:25:45 +000050 trace_env.sample->fp = fp;
Steve Blocka7e24c12009-10-30 11:49:00 +000051 // sp is only used to define stack high bound
52 trace_env.sample->sp =
Leon Clarked91b9f72010-01-27 17:25:45 +000053 reinterpret_cast<Address>(trace_env.sample) - 10240;
Steve Blocka7e24c12009-10-30 11:49:00 +000054 StackTracer::Trace(trace_env.sample);
55}
56
57
58// Hide c_entry_fp to emulate situation when sampling is done while
59// pure JS code is being executed
60static void DoTraceHideCEntryFPAddress(Address fp) {
61 v8::internal::Address saved_c_frame_fp = *(Top::c_entry_fp_address());
62 CHECK(saved_c_frame_fp);
63 *(Top::c_entry_fp_address()) = 0;
64 DoTrace(fp);
65 *(Top::c_entry_fp_address()) = saved_c_frame_fp;
66}
67
68
Steve Blocka7e24c12009-10-30 11:49:00 +000069// --- T r a c e E x t e n s i o n ---
70
71class TraceExtension : public v8::Extension {
72 public:
73 TraceExtension() : v8::Extension("v8/trace", kSource) { }
74 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
75 v8::Handle<String> name);
76 static v8::Handle<v8::Value> Trace(const v8::Arguments& args);
77 static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args);
78 static v8::Handle<v8::Value> JSEntrySP(const v8::Arguments& args);
79 static v8::Handle<v8::Value> JSEntrySPLevel2(const v8::Arguments& args);
80 private:
81 static Address GetFP(const v8::Arguments& args);
82 static const char* kSource;
83};
84
85
86const char* TraceExtension::kSource =
87 "native function trace();"
88 "native function js_trace();"
89 "native function js_entry_sp();"
90 "native function js_entry_sp_level2();";
91
92v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction(
93 v8::Handle<String> name) {
94 if (name->Equals(String::New("trace"))) {
95 return v8::FunctionTemplate::New(TraceExtension::Trace);
96 } else if (name->Equals(String::New("js_trace"))) {
97 return v8::FunctionTemplate::New(TraceExtension::JSTrace);
98 } else if (name->Equals(String::New("js_entry_sp"))) {
99 return v8::FunctionTemplate::New(TraceExtension::JSEntrySP);
100 } else if (name->Equals(String::New("js_entry_sp_level2"))) {
101 return v8::FunctionTemplate::New(TraceExtension::JSEntrySPLevel2);
102 } else {
103 CHECK(false);
104 return v8::Handle<v8::FunctionTemplate>();
105 }
106}
107
108
109Address TraceExtension::GetFP(const v8::Arguments& args) {
110 CHECK_EQ(1, args.Length());
111 // CodeGenerator::GenerateGetFramePointer pushes EBP / RBP value
112 // on stack. In 64-bit mode we can't use Smi operations code because
113 // they check that value is within Smi bounds.
114 Address fp = *reinterpret_cast<Address*>(*args[0]);
115 printf("Trace: %p\n", fp);
116 return fp;
117}
118
119
120v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) {
121 DoTrace(GetFP(args));
122 return v8::Undefined();
123}
124
125
126v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) {
127 DoTraceHideCEntryFPAddress(GetFP(args));
128 return v8::Undefined();
129}
130
131
132static Address GetJsEntrySp() {
133 CHECK_NE(NULL, Top::GetCurrentThread());
134 return Top::js_entry_sp(Top::GetCurrentThread());
135}
136
137
138v8::Handle<v8::Value> TraceExtension::JSEntrySP(const v8::Arguments& args) {
139 CHECK_NE(0, GetJsEntrySp());
140 return v8::Undefined();
141}
142
143
Steve Blocka7e24c12009-10-30 11:49:00 +0000144v8::Handle<v8::Value> TraceExtension::JSEntrySPLevel2(
145 const v8::Arguments& args) {
146 v8::HandleScope scope;
147 const Address js_entry_sp = GetJsEntrySp();
148 CHECK_NE(0, js_entry_sp);
149 CompileRun("js_entry_sp();");
150 CHECK_EQ(js_entry_sp, GetJsEntrySp());
151 return v8::Undefined();
152}
153
154
155static TraceExtension kTraceExtension;
156v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension);
157
158
159static void InitializeVM() {
160 if (env.IsEmpty()) {
161 v8::HandleScope scope;
162 const char* extensions[] = { "v8/trace" };
163 v8::ExtensionConfiguration config(1, extensions);
164 env = v8::Context::New(&config);
165 }
166 v8::HandleScope scope;
167 env->Enter();
168}
169
170
171static Handle<JSFunction> CompileFunction(const char* source) {
Steve Block6ded16b2010-05-10 14:33:55 +0100172 Handle<JSFunction> result(JSFunction::cast(
173 *v8::Utils::OpenHandle(*Script::Compile(String::New(source)))));
174 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000175}
176
177
178static Local<Value> GetGlobalProperty(const char* name) {
179 return env->Global()->Get(String::New(name));
180}
181
182
183static Handle<JSFunction> GetGlobalJSFunction(const char* name) {
Steve Block6ded16b2010-05-10 14:33:55 +0100184 Handle<JSFunction> result(JSFunction::cast(
185 *v8::Utils::OpenHandle(*GetGlobalProperty(name))));
186 return result;
Steve Blocka7e24c12009-10-30 11:49:00 +0000187}
188
189
Leon Clarkef7060e22010-06-03 12:02:55 +0100190static void CheckObjectIsJSFunction(const char* func_name,
191 Address addr) {
192 i::Object* obj = reinterpret_cast<i::Object*>(addr);
193 CHECK(obj->IsJSFunction());
194 CHECK(JSFunction::cast(obj)->shared()->name()->IsString());
195 i::SmartPointer<char> found_name =
196 i::String::cast(
197 JSFunction::cast(
198 obj)->shared()->name())->ToCString();
199 CHECK_EQ(func_name, *found_name);
Steve Blocka7e24c12009-10-30 11:49:00 +0000200}
201
202
203static void SetGlobalProperty(const char* name, Local<Value> value) {
204 env->Global()->Set(String::New(name), value);
205}
206
207
208static Handle<v8::internal::String> NewString(const char* s) {
209 return i::Factory::NewStringFromAscii(i::CStrVector(s));
210}
211
212
213namespace v8 {
214namespace internal {
215
216class CodeGeneratorPatcher {
217 public:
218 CodeGeneratorPatcher() {
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100219 CodeGenerator::InlineRuntimeLUT gen_get_frame_pointer =
Steve Block6ded16b2010-05-10 14:33:55 +0100220 {&CodeGenerator::GenerateGetFramePointer, "_GetFramePointer", 0};
221 // _RandomHeapNumber is just used as a dummy function that has zero
222 // arguments, the same as the _GetFramePointer function we actually patch
223 // in.
Steve Blocka7e24c12009-10-30 11:49:00 +0000224 bool result = CodeGenerator::PatchInlineRuntimeEntry(
Steve Block6ded16b2010-05-10 14:33:55 +0100225 NewString("_RandomHeapNumber"),
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100226 gen_get_frame_pointer, &old_inline_entry);
Steve Blocka7e24c12009-10-30 11:49:00 +0000227 CHECK(result);
228 }
229
230 ~CodeGeneratorPatcher() {
231 CHECK(CodeGenerator::PatchInlineRuntimeEntry(
232 NewString("_GetFramePointer"),
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100233 old_inline_entry, NULL));
Steve Blocka7e24c12009-10-30 11:49:00 +0000234 }
235
236 private:
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100237 CodeGenerator::InlineRuntimeLUT old_inline_entry;
Steve Blocka7e24c12009-10-30 11:49:00 +0000238};
239
240} } // namespace v8::internal
241
242
243// Creates a global function named 'func_name' that calls the tracing
244// function 'trace_func_name' with an actual EBP register value,
245// shifted right to be presented as Smi.
246static void CreateTraceCallerFunction(const char* func_name,
247 const char* trace_func_name) {
248 i::EmbeddedVector<char, 256> trace_call_buf;
249 i::OS::SNPrintF(trace_call_buf, "%s(%%_GetFramePointer());", trace_func_name);
250
251 // Compile the script.
252 i::CodeGeneratorPatcher patcher;
253 bool allow_natives_syntax = i::FLAG_allow_natives_syntax;
254 i::FLAG_allow_natives_syntax = true;
255 Handle<JSFunction> func = CompileFunction(trace_call_buf.start());
256 CHECK(!func.is_null());
257 i::FLAG_allow_natives_syntax = allow_natives_syntax;
Leon Clarkef7060e22010-06-03 12:02:55 +0100258 func->shared()->set_name(*NewString(func_name));
Steve Blocka7e24c12009-10-30 11:49:00 +0000259
260#ifdef DEBUG
261 v8::internal::Code* func_code = func->code();
262 CHECK(func_code->IsCode());
263 func_code->Print();
264#endif
265
266 SetGlobalProperty(func_name, v8::ToApi<Value>(func));
Steve Block6ded16b2010-05-10 14:33:55 +0100267 CHECK_EQ(*func, *GetGlobalJSFunction(func_name));
Steve Blocka7e24c12009-10-30 11:49:00 +0000268}
269
270
Steve Block6ded16b2010-05-10 14:33:55 +0100271// This test verifies that stack tracing works when called during
272// execution of a native function called from JS code. In this case,
273// StackTracer uses Top::c_entry_fp as a starting point for stack
274// walking.
Steve Blocka7e24c12009-10-30 11:49:00 +0000275TEST(CFromJSStackTrace) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100276 // TODO(711): The hack of replacing the inline runtime function
277 // RandomHeapNumber with GetFrameNumber does not work with the way
278 // the full compiler generates inline runtime calls.
279 i::FLAG_full_compiler = false;
Leon Clarkef7060e22010-06-03 12:02:55 +0100280 i::FLAG_always_full_compiler = false;
281
Steve Blocka7e24c12009-10-30 11:49:00 +0000282 TickSample sample;
283 InitTraceEnv(&sample);
284
285 InitializeVM();
286 v8::HandleScope scope;
Steve Block6ded16b2010-05-10 14:33:55 +0100287 // Create global function JSFuncDoTrace which calls
288 // extension function trace() with the current frame pointer value.
Steve Blocka7e24c12009-10-30 11:49:00 +0000289 CreateTraceCallerFunction("JSFuncDoTrace", "trace");
Steve Block6ded16b2010-05-10 14:33:55 +0100290 Local<Value> result = CompileRun(
Steve Blocka7e24c12009-10-30 11:49:00 +0000291 "function JSTrace() {"
292 " JSFuncDoTrace();"
293 "};\n"
Steve Block6ded16b2010-05-10 14:33:55 +0100294 "JSTrace();\n"
295 "true;");
296 CHECK(!result.IsEmpty());
297 // When stack tracer is invoked, the stack should look as follows:
298 // script [JS]
299 // JSTrace() [JS]
300 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
301 // trace(EBP encoded as Smi) [native (extension)]
302 // DoTrace(EBP) [native]
303 // StackTracer::Trace
Steve Blocka7e24c12009-10-30 11:49:00 +0000304 CHECK_GT(sample.frames_count, 1);
Steve Block6ded16b2010-05-10 14:33:55 +0100305 // Stack tracing will start from the first JS function, i.e. "JSFuncDoTrace"
Leon Clarkef7060e22010-06-03 12:02:55 +0100306 CheckObjectIsJSFunction("JSFuncDoTrace", sample.stack[0]);
307 CheckObjectIsJSFunction("JSTrace", sample.stack[1]);
Steve Blocka7e24c12009-10-30 11:49:00 +0000308}
309
310
Steve Block6ded16b2010-05-10 14:33:55 +0100311// This test verifies that stack tracing works when called during
312// execution of JS code. However, as calling StackTracer requires
313// entering native code, we can only emulate pure JS by erasing
314// Top::c_entry_fp value. In this case, StackTracer uses passed frame
315// pointer value as a starting point for stack walking.
Steve Blocka7e24c12009-10-30 11:49:00 +0000316TEST(PureJSStackTrace) {
Kristian Monsen80d68ea2010-09-08 11:05:35 +0100317 // TODO(711): The hack of replacing the inline runtime function
318 // RandomHeapNumber with GetFrameNumber does not work with the way
319 // the full compiler generates inline runtime calls.
320 i::FLAG_full_compiler = false;
Leon Clarkef7060e22010-06-03 12:02:55 +0100321 i::FLAG_always_full_compiler = false;
322
Steve Blocka7e24c12009-10-30 11:49:00 +0000323 TickSample sample;
324 InitTraceEnv(&sample);
325
326 InitializeVM();
327 v8::HandleScope scope;
Steve Block6ded16b2010-05-10 14:33:55 +0100328 // Create global function JSFuncDoTrace which calls
329 // extension function js_trace() with the current frame pointer value.
Steve Blocka7e24c12009-10-30 11:49:00 +0000330 CreateTraceCallerFunction("JSFuncDoTrace", "js_trace");
Steve Block6ded16b2010-05-10 14:33:55 +0100331 Local<Value> result = CompileRun(
Steve Blocka7e24c12009-10-30 11:49:00 +0000332 "function JSTrace() {"
333 " JSFuncDoTrace();"
334 "};\n"
335 "function OuterJSTrace() {"
336 " JSTrace();"
337 "};\n"
Steve Block6ded16b2010-05-10 14:33:55 +0100338 "OuterJSTrace();\n"
339 "true;");
340 CHECK(!result.IsEmpty());
341 // When stack tracer is invoked, the stack should look as follows:
342 // script [JS]
343 // OuterJSTrace() [JS]
344 // JSTrace() [JS]
345 // JSFuncDoTrace() [JS] [captures EBP value and encodes it as Smi]
346 // js_trace(EBP encoded as Smi) [native (extension)]
347 // DoTraceHideCEntryFPAddress(EBP) [native]
348 // StackTracer::Trace
349 //
350 // The last JS function called. It is only visible through
351 // sample.function, as its return address is above captured EBP value.
Leon Clarked91b9f72010-01-27 17:25:45 +0000352 CHECK_EQ(GetGlobalJSFunction("JSFuncDoTrace")->address(),
353 sample.function);
Steve Blocka7e24c12009-10-30 11:49:00 +0000354 CHECK_GT(sample.frames_count, 1);
355 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace"
Leon Clarkef7060e22010-06-03 12:02:55 +0100356 CheckObjectIsJSFunction("JSTrace", sample.stack[0]);
357 CheckObjectIsJSFunction("OuterJSTrace", sample.stack[1]);
Steve Blocka7e24c12009-10-30 11:49:00 +0000358}
359
360
Steve Blockd0582a62009-12-15 09:54:21 +0000361static void CFuncDoTrace(byte dummy_parameter) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000362 Address fp;
363#ifdef __GNUC__
364 fp = reinterpret_cast<Address>(__builtin_frame_address(0));
Steve Blockd0582a62009-12-15 09:54:21 +0000365#elif defined _MSC_VER
366 // Approximate a frame pointer address. We compile without base pointers,
367 // so we can't trust ebp/rbp.
368 fp = &dummy_parameter - 2 * sizeof(void*); // NOLINT
369#else
370#error Unexpected platform.
Steve Blocka7e24c12009-10-30 11:49:00 +0000371#endif
372 DoTrace(fp);
373}
374
375
376static int CFunc(int depth) {
377 if (depth <= 0) {
Steve Blockd0582a62009-12-15 09:54:21 +0000378 CFuncDoTrace(0);
Steve Blocka7e24c12009-10-30 11:49:00 +0000379 return 0;
380 } else {
381 return CFunc(depth - 1) + 1;
382 }
383}
384
385
Steve Block6ded16b2010-05-10 14:33:55 +0100386// This test verifies that stack tracing doesn't crash when called on
387// pure native code. StackTracer only unrolls JS code, so we can't
388// get any meaningful info here.
Steve Blocka7e24c12009-10-30 11:49:00 +0000389TEST(PureCStackTrace) {
390 TickSample sample;
391 InitTraceEnv(&sample);
392 // Check that sampler doesn't crash
393 CHECK_EQ(10, CFunc(10));
394}
395
396
397TEST(JsEntrySp) {
398 InitializeVM();
399 v8::HandleScope scope;
400 CHECK_EQ(0, GetJsEntrySp());
401 CompileRun("a = 1; b = a + 1;");
402 CHECK_EQ(0, GetJsEntrySp());
403 CompileRun("js_entry_sp();");
404 CHECK_EQ(0, GetJsEntrySp());
405 CompileRun("js_entry_sp_level2();");
406 CHECK_EQ(0, GetJsEntrySp());
407}
408
409#endif // ENABLE_LOGGING_AND_PROFILING