OLD | NEW |
(Empty) | |
| 1 // 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 |
| 18 using v8::Function; |
| 19 using v8::Local; |
| 20 using v8::Object; |
| 21 using v8::Script; |
| 22 using v8::String; |
| 23 using v8::Value; |
| 24 |
| 25 using v8::internal::byte; |
| 26 using v8::internal::Address; |
| 27 using v8::internal::Handle; |
| 28 using v8::internal::JSFunction; |
| 29 using v8::internal::StackTracer; |
| 30 using v8::internal::TickSample; |
| 31 using v8::internal::Top; |
| 32 |
| 33 namespace i = v8::internal; |
| 34 |
| 35 |
| 36 static v8::Persistent<v8::Context> env; |
| 37 |
| 38 |
| 39 static struct { |
| 40 TickSample* sample; |
| 41 } trace_env = { NULL }; |
| 42 |
| 43 |
| 44 static void InitTraceEnv(TickSample* sample) { |
| 45 trace_env.sample = sample; |
| 46 } |
| 47 |
| 48 |
| 49 static void DoTrace(Address fp) { |
| 50 trace_env.sample->fp = reinterpret_cast<uintptr_t>(fp); |
| 51 // sp is only used to define stack high bound |
| 52 trace_env.sample->sp = |
| 53 reinterpret_cast<uintptr_t>(trace_env.sample) - 10240; |
| 54 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 |
| 60 static 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 |
| 69 static void CheckRetAddrIsInFunction(const char* func_name, |
| 70 Address ret_addr, |
| 71 Address func_start_addr, |
| 72 unsigned int func_len) { |
| 73 printf("CheckRetAddrIsInFunction \"%s\": %p %p %p\n", |
| 74 func_name, func_start_addr, ret_addr, func_start_addr + func_len); |
| 75 CHECK_GE(ret_addr, func_start_addr); |
| 76 CHECK_GE(func_start_addr + func_len, ret_addr); |
| 77 } |
| 78 |
| 79 |
| 80 static void CheckRetAddrIsInJSFunction(const char* func_name, |
| 81 Address ret_addr, |
| 82 Handle<JSFunction> func) { |
| 83 v8::internal::Code* func_code = func->code(); |
| 84 CheckRetAddrIsInFunction( |
| 85 func_name, ret_addr, |
| 86 func_code->instruction_start(), |
| 87 func_code->ExecutableSize()); |
| 88 } |
| 89 |
| 90 |
| 91 // --- T r a c e E x t e n s i o n --- |
| 92 |
| 93 class TraceExtension : public v8::Extension { |
| 94 public: |
| 95 TraceExtension() : v8::Extension("v8/trace", kSource) { } |
| 96 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( |
| 97 v8::Handle<String> name); |
| 98 static v8::Handle<v8::Value> Trace(const v8::Arguments& args); |
| 99 static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args); |
| 100 static v8::Handle<v8::Value> JSEntrySP(const v8::Arguments& args); |
| 101 static v8::Handle<v8::Value> JSEntrySPLevel2(const v8::Arguments& args); |
| 102 private: |
| 103 static Address GetFP(const v8::Arguments& args); |
| 104 static const char* kSource; |
| 105 }; |
| 106 |
| 107 |
| 108 const char* TraceExtension::kSource = |
| 109 "native function trace();" |
| 110 "native function js_trace();" |
| 111 "native function js_entry_sp();" |
| 112 "native function js_entry_sp_level2();"; |
| 113 |
| 114 v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction( |
| 115 v8::Handle<String> name) { |
| 116 if (name->Equals(String::New("trace"))) { |
| 117 return v8::FunctionTemplate::New(TraceExtension::Trace); |
| 118 } else if (name->Equals(String::New("js_trace"))) { |
| 119 return v8::FunctionTemplate::New(TraceExtension::JSTrace); |
| 120 } else if (name->Equals(String::New("js_entry_sp"))) { |
| 121 return v8::FunctionTemplate::New(TraceExtension::JSEntrySP); |
| 122 } else if (name->Equals(String::New("js_entry_sp_level2"))) { |
| 123 return v8::FunctionTemplate::New(TraceExtension::JSEntrySPLevel2); |
| 124 } else { |
| 125 CHECK(false); |
| 126 return v8::Handle<v8::FunctionTemplate>(); |
| 127 } |
| 128 } |
| 129 |
| 130 |
| 131 Address TraceExtension::GetFP(const v8::Arguments& args) { |
| 132 CHECK_EQ(1, args.Length()); |
| 133 // CodeGenerator::GenerateGetFramePointer pushes EBP / RBP value |
| 134 // on stack. In 64-bit mode we can't use Smi operations code because |
| 135 // they check that value is within Smi bounds. |
| 136 Address fp = *reinterpret_cast<Address*>(*args[0]); |
| 137 printf("Trace: %p\n", fp); |
| 138 return fp; |
| 139 } |
| 140 |
| 141 |
| 142 v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) { |
| 143 DoTrace(GetFP(args)); |
| 144 return v8::Undefined(); |
| 145 } |
| 146 |
| 147 |
| 148 v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) { |
| 149 DoTraceHideCEntryFPAddress(GetFP(args)); |
| 150 return v8::Undefined(); |
| 151 } |
| 152 |
| 153 |
| 154 static Address GetJsEntrySp() { |
| 155 CHECK_NE(NULL, Top::GetCurrentThread()); |
| 156 return Top::js_entry_sp(Top::GetCurrentThread()); |
| 157 } |
| 158 |
| 159 |
| 160 v8::Handle<v8::Value> TraceExtension::JSEntrySP(const v8::Arguments& args) { |
| 161 CHECK_NE(0, GetJsEntrySp()); |
| 162 return v8::Undefined(); |
| 163 } |
| 164 |
| 165 |
| 166 static void CompileRun(const char* source) { |
| 167 Script::Compile(String::New(source))->Run(); |
| 168 } |
| 169 |
| 170 |
| 171 v8::Handle<v8::Value> TraceExtension::JSEntrySPLevel2( |
| 172 const v8::Arguments& args) { |
| 173 v8::HandleScope scope; |
| 174 const Address js_entry_sp = GetJsEntrySp(); |
| 175 CHECK_NE(0, js_entry_sp); |
| 176 CompileRun("js_entry_sp();"); |
| 177 CHECK_EQ(js_entry_sp, GetJsEntrySp()); |
| 178 return v8::Undefined(); |
| 179 } |
| 180 |
| 181 |
| 182 static TraceExtension kTraceExtension; |
| 183 v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension); |
| 184 |
| 185 |
| 186 static void InitializeVM() { |
| 187 if (env.IsEmpty()) { |
| 188 v8::HandleScope scope; |
| 189 const char* extensions[] = { "v8/trace" }; |
| 190 v8::ExtensionConfiguration config(1, extensions); |
| 191 env = v8::Context::New(&config); |
| 192 } |
| 193 v8::HandleScope scope; |
| 194 env->Enter(); |
| 195 } |
| 196 |
| 197 |
| 198 static Handle<JSFunction> CompileFunction(const char* source) { |
| 199 return v8::Utils::OpenHandle(*Script::Compile(String::New(source))); |
| 200 } |
| 201 |
| 202 |
| 203 static Local<Value> GetGlobalProperty(const char* name) { |
| 204 return env->Global()->Get(String::New(name)); |
| 205 } |
| 206 |
| 207 |
| 208 static Handle<JSFunction> GetGlobalJSFunction(const char* name) { |
| 209 Handle<JSFunction> js_func(JSFunction::cast( |
| 210 *(v8::Utils::OpenHandle( |
| 211 *GetGlobalProperty(name))))); |
| 212 return js_func; |
| 213 } |
| 214 |
| 215 |
| 216 static void CheckRetAddrIsInJSFunction(const char* func_name, |
| 217 Address ret_addr) { |
| 218 CheckRetAddrIsInJSFunction(func_name, ret_addr, |
| 219 GetGlobalJSFunction(func_name)); |
| 220 } |
| 221 |
| 222 |
| 223 static void SetGlobalProperty(const char* name, Local<Value> value) { |
| 224 env->Global()->Set(String::New(name), value); |
| 225 } |
| 226 |
| 227 |
| 228 static Handle<v8::internal::String> NewString(const char* s) { |
| 229 return i::Factory::NewStringFromAscii(i::CStrVector(s)); |
| 230 } |
| 231 |
| 232 |
| 233 namespace v8 { |
| 234 namespace internal { |
| 235 |
| 236 class CodeGeneratorPatcher { |
| 237 public: |
| 238 CodeGeneratorPatcher() { |
| 239 CodeGenerator::InlineRuntimeLUT genGetFramePointer = |
| 240 {&CodeGenerator::GenerateGetFramePointer, "_GetFramePointer"}; |
| 241 // _FastCharCodeAt is not used in our tests. |
| 242 bool result = CodeGenerator::PatchInlineRuntimeEntry( |
| 243 NewString("_FastCharCodeAt"), |
| 244 genGetFramePointer, &oldInlineEntry); |
| 245 CHECK(result); |
| 246 } |
| 247 |
| 248 ~CodeGeneratorPatcher() { |
| 249 CHECK(CodeGenerator::PatchInlineRuntimeEntry( |
| 250 NewString("_GetFramePointer"), |
| 251 oldInlineEntry, NULL)); |
| 252 } |
| 253 |
| 254 private: |
| 255 CodeGenerator::InlineRuntimeLUT oldInlineEntry; |
| 256 }; |
| 257 |
| 258 } } // namespace v8::internal |
| 259 |
| 260 |
| 261 // Creates a global function named 'func_name' that calls the tracing |
| 262 // function 'trace_func_name' with an actual EBP register value, |
| 263 // shifted right to be presented as Smi. |
| 264 static void CreateTraceCallerFunction(const char* func_name, |
| 265 const char* trace_func_name) { |
| 266 i::EmbeddedVector<char, 256> trace_call_buf; |
| 267 i::OS::SNPrintF(trace_call_buf, "%s(%%_GetFramePointer());", trace_func_name); |
| 268 |
| 269 // Compile the script. |
| 270 i::CodeGeneratorPatcher patcher; |
| 271 bool allow_natives_syntax = i::FLAG_allow_natives_syntax; |
| 272 i::FLAG_allow_natives_syntax = true; |
| 273 Handle<JSFunction> func = CompileFunction(trace_call_buf.start()); |
| 274 CHECK(!func.is_null()); |
| 275 i::FLAG_allow_natives_syntax = allow_natives_syntax; |
| 276 |
| 277 #ifdef DEBUG |
| 278 v8::internal::Code* func_code = func->code(); |
| 279 CHECK(func_code->IsCode()); |
| 280 func_code->Print(); |
| 281 #endif |
| 282 |
| 283 SetGlobalProperty(func_name, v8::ToApi<Value>(func)); |
| 284 } |
| 285 |
| 286 |
| 287 TEST(CFromJSStackTrace) { |
| 288 TickSample sample; |
| 289 InitTraceEnv(&sample); |
| 290 |
| 291 InitializeVM(); |
| 292 v8::HandleScope scope; |
| 293 CreateTraceCallerFunction("JSFuncDoTrace", "trace"); |
| 294 CompileRun( |
| 295 "function JSTrace() {" |
| 296 " JSFuncDoTrace();" |
| 297 "};\n" |
| 298 "JSTrace();"); |
| 299 CHECK_GT(sample.frames_count, 1); |
| 300 // Stack sampling will start from the first JS function, i.e. "JSFuncDoTrace" |
| 301 CheckRetAddrIsInJSFunction("JSFuncDoTrace", |
| 302 sample.stack[0]); |
| 303 CheckRetAddrIsInJSFunction("JSTrace", |
| 304 sample.stack[1]); |
| 305 } |
| 306 |
| 307 |
| 308 TEST(PureJSStackTrace) { |
| 309 TickSample sample; |
| 310 InitTraceEnv(&sample); |
| 311 |
| 312 InitializeVM(); |
| 313 v8::HandleScope scope; |
| 314 CreateTraceCallerFunction("JSFuncDoTrace", "js_trace"); |
| 315 CompileRun( |
| 316 "function JSTrace() {" |
| 317 " JSFuncDoTrace();" |
| 318 "};\n" |
| 319 "function OuterJSTrace() {" |
| 320 " JSTrace();" |
| 321 "};\n" |
| 322 "OuterJSTrace();"); |
| 323 CHECK_GT(sample.frames_count, 1); |
| 324 // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace" |
| 325 CheckRetAddrIsInJSFunction("JSTrace", |
| 326 sample.stack[0]); |
| 327 CheckRetAddrIsInJSFunction("OuterJSTrace", |
| 328 sample.stack[1]); |
| 329 } |
| 330 |
| 331 |
| 332 static void CFuncDoTrace() { |
| 333 Address fp; |
| 334 #ifdef __GNUC__ |
| 335 fp = reinterpret_cast<Address>(__builtin_frame_address(0)); |
| 336 #elif defined _MSC_VER && defined V8_TARGET_ARCH_IA32 |
| 337 __asm mov [fp], ebp // NOLINT |
| 338 #elif defined _MSC_VER && defined V8_TARGET_ARCH_X64 |
| 339 // FIXME: I haven't really tried to compile it. |
| 340 __asm movq [fp], rbp // NOLINT |
| 341 #endif |
| 342 DoTrace(fp); |
| 343 } |
| 344 |
| 345 |
| 346 static int CFunc(int depth) { |
| 347 if (depth <= 0) { |
| 348 CFuncDoTrace(); |
| 349 return 0; |
| 350 } else { |
| 351 return CFunc(depth - 1) + 1; |
| 352 } |
| 353 } |
| 354 |
| 355 |
| 356 TEST(PureCStackTrace) { |
| 357 TickSample sample; |
| 358 InitTraceEnv(&sample); |
| 359 // Check that sampler doesn't crash |
| 360 CHECK_EQ(10, CFunc(10)); |
| 361 } |
| 362 |
| 363 |
| 364 TEST(JsEntrySp) { |
| 365 InitializeVM(); |
| 366 v8::HandleScope scope; |
| 367 CHECK_EQ(0, GetJsEntrySp()); |
| 368 CompileRun("a = 1; b = a + 1;"); |
| 369 CHECK_EQ(0, GetJsEntrySp()); |
| 370 CompileRun("js_entry_sp();"); |
| 371 CHECK_EQ(0, GetJsEntrySp()); |
| 372 CompileRun("js_entry_sp_level2();"); |
| 373 CHECK_EQ(0, GetJsEntrySp()); |
| 374 } |
| 375 |
| 376 #endif // ENABLE_LOGGING_AND_PROFILING |
OLD | NEW |