Why should I know this?

v8 인터프리터 분석 - 함수 호출 본문

v8

v8 인터프리터 분석 - 함수 호출

die4taoam 2019. 3. 15. 06:55


v8 version

$ git describe --tags
3.29.86-35352-g2e3f3950ef


TOTAL TIME : FUNCTION
:=== Function Call Graph for 'v8::internal::Runtime_TraceEnter' ===
:========== Back-trace ==========
497.005 us : ├─(2) v8::internal::Runtime_TraceEnter
497.005 us : │▶(2) v8::internal::_GLOBAL__N_1::Invoke
: │
421.405 us : ├─(2) v8::internal::Runtime_TraceEnter
421.405 us : │▶(2) v8::internal::_GLOBAL__N_1::Invoke
: │
913.708 ms : ├─(2501) v8::internal::Runtime_TraceEnter
913.708 ms : │▶(2501) v8::internal::_GLOBAL__N_1::Invoke
: │
19.143 ms : ├─(56) v8::internal::Runtime_TraceEnter
19.143 ms : │▶(56) v8::internal::_GLOBAL__N_1::Invoke
: │
20.640 ms : ├─(66) v8::internal::Runtime_TraceEnter
20.640 ms : │▶(66) v8::internal::_GLOBAL__N_1::Invoke
: │
514.638 us : ├─(2) v8::internal::Runtime_TraceEnter
514.638 us : │▶(2) v8::internal::_GLOBAL__N_1::Invoke
: │
224.139 us : ├─(1) v8::internal::Runtime_TraceEnter
224.139 us : │▶(1) v8::internal::_GLOBAL__N_1::Invoke
: │
36.722 ms : ├─(81) v8::internal::Runtime_TraceEnter
36.722 ms : │▶(81) v8::internal::_GLOBAL__N_1::Invoke
: │
385.806 us : ├─(1) v8::internal::Runtime_TraceEnter
385.806 us : │▶(1) v8::internal::_GLOBAL__N_1::Invoke
: │
232.193 us : ├─(1) v8::internal::Runtime_TraceEnter
232.193 us : │▶(1) v8::internal::_GLOBAL__N_1::Invoke
: │
311.523 us : └─(1) v8::internal::Runtime_TraceEnter
:========== Call Graph ==========
992.801 ms : (2714) v8::internal::Runtime_TraceEnter
1.194 ms : ├─(2714) v8::internal::ClobberDoubleRegisters
: │
499.510 us : ├─(2714) v8::internal::Arguments::Arguments
: │
6.358 ms : ├▶(2714) v8::internal::SealHandleScope::SealHandleScope
: │
375.540 ms : ├▶(2714) v8::internal::_GLOBAL__N_1::StackSize
: │
110.261 ms : ├▶(5428) v8::internal::PrintF
: │
482.360 ms : ├▶(2714) v8::internal::JavaScriptFrame::PrintTop
: │
724.617 us : └─(2714) v8::internal::SealHandleScope::~SealHandleScope


v8 트레이싱 결과

|-> v8::internall:Execution::Call
|-> v8::internal::_GLOBAL__N_1:Invoke
|-> v8::internal::Runtime_TraceEnter

모든 함수 호출은 v8::internal::Execution::Call을 통함.


execution.cc:354


// static
MaybeHandle<Object> Execution::Call(Isolate* isolate, Handle<Object> callable,
Handle<Object> receiver, int argc,
Handle<Object> argv[]) {
return Invoke(isolate, InvokeParams::SetUpForCall(isolate, callable, receiver,
argc, argv));
}


execution.cc:170


V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
const InvokeParams& params) {

// 주요 코드들
Handle<Code> code =
JSEntry(isolate, params.execution_target, params.is_construct);

if (params.execution_target == Execution::Target::kCallable) {
// clang-format off
// {new_target}, {target}, {receiver}, return value: tagged pointers
// {argv}: pointer to array of tagged pointers
using JSEntryFunction = GeneratedCode<Address(
Address root_register_value, Address new_target, Address target,
Address receiver, intptr_t argc, Address** argv)>;
// clang-format on
JSEntryFunction stub_entry =
JSEntryFunction::FromAddress(isolate, code->InstructionStart());

Address orig_func = params.new_target->ptr();
Address func = params.target->ptr();
Address recv = params.receiver->ptr();
Address** argv = reinterpret_cast<Address**>(params.argv);
RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);

// 여기에서 데이터들을 살펴보자.
value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
orig_func, func, recv, params.argc, argv));


stub_entry.Call을 호출하면 바로 interpreting이 진행되므로

여기서 각 인자들이 어떤 타입을 갖는지 확인해보자.



(gdb) p stub_entry
$3 = {<v8::internal::GeneratedCode<unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, long, unsigned long**>> = {
isolate_ = 0x5555556041a0, fn_ptr_ = 0x7ffff78c7360 <Builtins_JSEntry>}, <No data fields>}

(gdb) call _v8_internal_Print_Object(*(v8::internal::Object **)params.new_target)
#undefined

(gdb) call _v8_internal_Print_Object(*(v8::internal::Object **)params.target)
0x2624e461e9b1: [Function] in OldSpace
- map: 0x2bc48a1003b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2624e4601ff1 <JSFunction (sfi = 0x1d0c6d087d51)>
- elements: 0x3cc2a2e00c21 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x2624e461e829 <SharedFunctionInfo>
- name: 0x3cc2a2e00751 <String[#0]: >
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x2624e4601749 <NativeContext[247]>
- code: 0x3f0bfdd04381 <Code BUILTIN InterpreterEntryTrampoline>
- interpreted
- bytecode: 0x2624e461e919 <BytecodeArray[43]>
- source code: function find_me_c() {
console.log("Hello C");
}
find_me_c();
find_me_c();


- properties: 0x3cc2a2e00c21 <FixedArray[0]> {
#length: 0x1d0c6d0804b9 <AccessorInfo> (const accessor descriptor)
#name: 0x1d0c6d080449 <AccessorInfo> (const accessor descriptor)
#arguments: 0x1d0c6d080369 <AccessorInfo> (const accessor descriptor)
#caller: 0x1d0c6d0803d9 <AccessorInfo> (const accessor descriptor)
#prototype: 0x1d0c6d080529 <AccessorInfo> (const accessor descriptor)
}

- feedback vector: 0x2624e461e9f1: [FeedbackVector] in OldSpace
- map: 0x3cc2a2e00bc1 <Map>
- length: 3
- shared function info: 0x2624e461e829 <SharedFunctionInfo>
- optimized code/marker: OptimizationMarker::kNone
- invocation count: 0
- profiler ticks: 0
- slot #0 LoadGlobalNotInsideTypeof UNINITIALIZED {
[0]: [cleared]
[1]: 0x3cc2a2e04da9 <Symbol: (uninitialized_symbol)>
}
- slot #2 kCreateClosure {
[2]: 0x2624e461ea31 <FeedbackCell[no feedback]>
}


(gdb) p params.receiver
$10 = {<v8::internal::HandleBase> = {location_ = 0x555555696518}, <No data fields>}
(gdb) ptype params.receiver

(gdb) call _v8_internal_Print_Object(*(v8::internal::Object **)params.receiver)
0x362cc7c01521: [JSGlobalProxy]
- map: 0x2bc48a108069 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2624e461a939 <JSObject>
- elements: 0x3cc2a2e00c21 <FixedArray[0]> [HOLEY_ELEMENTS]
- native context: 0x2624e4601749 <NativeContext[247]>
- properties: 0x3cc2a2e00c21 <FixedArray[0]> {}



new_target은 invoke가 호출되기 전에 다음처럼 할당된다.


// static
InvokeParams InvokeParams::SetUpForCall(Isolate* isolate,
Handle<Object> callable,
Handle<Object> receiver, int argc,
Handle<Object>* argv) {
InvokeParams params;
params.target = callable;
params.receiver = NormalizeReceiver(isolate, receiver);
params.argc = argc;
params.argv = argv;
params.new_target = isolate->factory()->undefined_value();
params.microtask_queue = nullptr;


인자로 JSFunction target의 주소, JSGlobalProxy receiver의 주소가 전달된다.



Address orig_func = params.new_target->ptr();
Address func = params.target->ptr();
Address recv = params.receiver->ptr();

(gdb) p/x orig_func
$14 = 0x3cc2a2e004d1
(gdb) p/x func
$15 = 0x2624e461e9b1
(gdb) p/x recv
$16 = 0x362cc7c01521


stub_entry는 execution.cc:155에서 찾을 수 있다.


Handle<Code> JSEntry(Isolate* isolate, Execution::Target execution_target,
bool is_construct) {
if (is_construct) {
DCHECK_EQ(Execution::Target::kCallable, execution_target);
return BUILTIN_CODE(isolate, JSConstructEntry);
} else if (execution_target == Execution::Target::kCallable) {
DCHECK(!is_construct);
return BUILTIN_CODE(isolate, JSEntry);
} else if (execution_target == Execution::Target::kRunMicrotasks) {
DCHECK(!is_construct);
return BUILTIN_CODE(isolate, JSRunMicrotasksEntry);
}
UNREACHABLE();
}


하지만 이 역시 추적에 용이한 소스는 아니다.


// Convenience macro to avoid generating named accessors for all builtins.
#define BUILTIN_CODE(isolate, name) \
(isolate)->builtins()->builtin_handle(Builtins::k##name)


대신 디버거를 통해 파악해볼 수 있다.



(gdb) call _v8_internal_Print_Object(* (v8:: internal:: Object **)code)
0x3f0bfdd03cc1: [Code]
- map: 0x3cc2a2e009e1 < Map >
kind = BUILTIN
name = JSEntry
compiler = unknown
address = 0x7fffffffc970

Trampoline(size = 13)
0x3f0bfdd03d00 0 49ba60738cf7ff7f0000 REX.W movq r10, 0x7ffff78c7360(JSEntry)
0x3f0bfdd03d0a a 41ffe2 jmp r10

Instructions(size = 180)
0x7ffff78c7360 0 55 push rbp
0x7ffff78c7361 1 4889e5 REX.W movq rbp, rsp
0x7ffff78c7364 4 6a02 push 0x2
0x7ffff78c7366 6 4883ec08 REX.W subq rsp, 0x8
0x7ffff78c736a a 4154 push r12
0x7ffff78c736c c 4155 push r13
0x7ffff78c736e e 4156 push r14
0x7ffff78c7370 10 4157 push r15
0x7ffff78c7372 12 53 push rbx
0x7ffff78c7373 13 4c8bef REX.W movq r13, rdi
0x7ffff78c7376 16 41ffb5d02f0000 push[r13 + 0x2fd0](external value(Isolate:: c_entry_fp_address))
0x7ffff78c737d 1d 4d8b95782f0000 REX.W movq r10, [r13 + 0x2f78](external value(Isolate:: context_address))
0x7ffff78c7384 24 4c8955f0 REX.W movq[rbp - 0x10], r10
0x7ffff78c7388 28 498b85f82f0000 REX.W movq rax, [r13 + 0x2ff8](external value(Isolate:: js_entry_sp_address))
0x7ffff78c738f 2f 4885c0 REX.W testq rax, rax
0x7ffff78c7392 32 0f8511000000 jnz 0x7ffff78c73a9(JSEntry)
0x7ffff78c7398 38 6a02 push 0x2
0x7ffff78c739a 3a 488bc5 REX.W movq rax, rbp
0x7ffff78c739d 3d 498985f82f0000 REX.W movq[r13 + 0x2ff8](external value(Isolate:: js_entry_sp_address)), rax
0x7ffff78c73a4 44 e902000000 jmp 0x7ffff78c73ab(JSEntry)
0x7ffff78c73a9 49 6a00 push 0x0
0x7ffff78c73ab 4b e913000000 jmp 0x7ffff78c73c3(JSEntry)
0x7ffff78c73b0 50 498985882f0000 REX.W movq[r13 + 0x2f88](external value(Isolate:: pending_exception_address)), rax
0x7ffff78c73b7 57 498b85b8000000 REX.W movq rax, [r13 + 0xb8](root(exception))
0x7ffff78c73be 5e e920000000 jmp 0x7ffff78c73e3(JSEntry)
0x7ffff78c73c3 63 6a00 push 0x0
0x7ffff78c73c5 65 41ffb5d82f0000 push[r13 + 0x2fd8](external value(Isolate:: handler_address))
0x7ffff78c73cc 6c 4989a5d82f0000 REX.W movq[r13 + 0x2fd8](external value(Isolate:: handler_address)), rsp
0x7ffff78c73d3 73 e828020000 call 0x7ffff78c7600(JSEntryTrampoline)
0x7ffff78c73d8 78 418f85d82f0000 pop[r13 + 0x2fd8](external value(Isolate:: handler_address))
0x7ffff78c73df 7f 4883c408 REX.W addq rsp, 0x8
0x7ffff78c73e3 83 5b pop rbx
0x7ffff78c73e4 84 4883fb02 REX.W cmpq rbx, 0x2
0x7ffff78c73e8 88 0f850e000000 jnz 0x7ffff78c73fc(JSEntry)
0x7ffff78c73ee 8e 4d8d95f82f0000 REX.W leaq r10, [r13 + 0x2ff8](external value(Isolate:: js_entry_sp_address))
0x7ffff78c73f5 95 49c70200000000 REX.W movq[r10], 0x0
0x7ffff78c73fc 9c 418f85d02f0000 pop[r13 + 0x2fd0](external value(Isolate:: c_entry_fp_address))
0x7ffff78c7403 a3 5b pop rbx
0x7ffff78c7404 a4 415f pop r15
0x7ffff78c7406 a6 415e pop r14
0x7ffff78c7408 a8 415d pop r13
0x7ffff78c740a aa 415c pop r12
0x7ffff78c740c ac 4883c410 REX.W addq rsp, 0x10
0x7ffff78c7410 b0 5d pop rbp
0x7ffff78c7411 b1 c3 retl
0x7ffff78c7412 b2 cc int3l
0x7ffff78c7413 b3 cc int3l


Handler Table(size = 1)

RelocInfo(size = 2)
0x3f0bfdd03d02 off heap target


이후의 실행 과정은 이렇다.



(gdb) bt
#0 v8::internal::Runtime_TraceEnter (args_length=0, args_object=0x7fffffffcf70, isolate=0x555555604200) at ../../src/runtime/runtime-test.cc:776
#1 0x00007ffff7bbbcc4 in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit () from /home/m/v8/v8/out/x64.debug/./libv8.so
#2 0x00007ffff7d27072 in Builtins_CallRuntimeHandler () from /home/m/v8/v8/out/x64.debug/./libv8.so
#3 0x00007fffffffcf28 in ?? ()
#4 0x00007fffffffcf28 in ?? ()
#5 0x00007fffffffcf98 in ?? ()
#6 0x00007fffffffcf98 in ?? ()
#7 0x0000000000000018 in ?? ()
#8 0x00007fffffffcf98 in ?? ()
#9 0x00007ffff78d1f48 in Builtins_InterpreterEntryTrampoline () from /home/m/v8/v8/out/x64.debug/./libv8.so
#10 0x00003e83385004d1 in ?? ()
#11 0x00003e83385004d1 in ?? ()
#12 0x00003e83385004d1 in ?? ()
#13 0x0000003900000000 in ?? ()
#14 0x000019dc7529eb61 in ?? ()
#15 0x000019dc7529ea59 in ?? ()
#16 0x000019dc75281749 in ?? ()
#17 0x00007fffffffcff0 in ?? ()
#18 0x00007ffff78d1f48 in Builtins_InterpreterEntryTrampoline () from /home/m/v8/v8/out/x64.debug/./libv8.so
#19 0x000034437cd81521 in ?? ()
#20 0x000019dc7529e9b9 in ?? ()
#21 0x00003e83385004d1 in ?? ()
#22 0x000019dc7529ea59 in ?? ()
#23 0x00003e83385004d1 in ?? ()
#24 0x0000005600000000 in ?? ()
#25 0x000019dc7529e919 in ?? ()
#26 0x000019dc7529e9b9 in ?? ()
#27 0x000019dc75281749 in ?? ()
#28 0x00007fffffffd018 in ?? ()
#29 0x00007ffff78c771d in Builtins_JSEntryTrampoline () from /home/m/v8/v8/out/x64.debug/./libv8.so
#30 0x000034437cd81521 in ?? ()
#31 0x000019dc7529e9b9 in ?? ()
#32 0x0000000000000020 in ?? ()
#33 0x00007fffffffd080 in ?? ()
#34 0x00007ffff78c7498 in Builtins_JSEntry () from /home/m/v8/v8/out/x64.debug/./libv8.so


소스코드의 추적은 안되기 때문에 키워드를 검색해야 한다.



$ grep "JSEntryTrampoline" -nR src/



$ grep "JSEntry" -nR src/builtins/x64/
src/builtins/x64/builtins-x64.cc:367:// using JSEntryFunction = GeneratedCode<Address(
src/builtins/x64/builtins-x64.cc:371:// using JSEntryFunction = GeneratedCode<Address(
src/builtins/x64/builtins-x64.cc:373:void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
src/builtins/x64/builtins-x64.cc:441: IsolateAddressId::kJSEntrySPAddress, masm->isolate());
src/builtins/x64/builtins-x64.cc:461: masm->isolate()->builtins()->SetJSEntryHandlerOffset(handler_entry.pos());
src/builtins/x64/builtins-x64.cc:534:void Builtins::Generate_JSEntry(MacroAssembler* masm) {
src/builtins/x64/builtins-x64.cc:535: Generate_JSEntryVariant(masm, StackFrame::ENTRY,
src/builtins/x64/builtins-x64.cc:536: Builtins::kJSEntryTrampoline);
src/builtins/x64/builtins-x64.cc:540: Generate_JSEntryVariant(masm, StackFrame::CONSTRUCT_ENTRY,
src/builtins/x64/builtins-x64.cc:545: Generate_JSEntryVariant(masm, StackFrame::ENTRY,
src/builtins/x64/builtins-x64.cc:549:static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm,
src/builtins/x64/builtins-x64.cc:668:void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) {
src/builtins/x64/builtins-x64.cc:669: Generate_JSEntryTrampolineHelper(masm, false);
src/builtins/x64/builtins-x64.cc:673: Generate_JSEntryTrampolineHelper(masm, true);


100% 확신할 수는 없지만,



void Builtins::Generate_JSEntry(MacroAssembler* masm) {
Generate_JSEntryVariant(masm, StackFrame::ENTRY,
Builtins::kJSEntryTrampoline);
}



// Called with the native C calling convention. The corresponding function
// signature is either:
// using JSEntryFunction = GeneratedCode<Address(
// Address root_register_value, Address new_target, Address target,
// Address receiver, intptr_t argc, Address** argv)>;
// or
// using JSEntryFunction = GeneratedCode<Address(
// Address root_register_value, MicrotaskQueue* microtask_queue)>;
void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
Builtins::Name entry_trampoline) {


위의 코드 내부에서 생성하는 어셈과 Builtins_JSEntry와 같음을 확인 할 수 있다.





'v8' 카테고리의 다른 글

[졸간분] 최적화 기법 on-stack replacement  (0) 2019.11.14
Nodejs 최적화 Tip?  (0) 2019.11.13
Javascript Interpreting 분석  (0) 2019.06.08
Build and Test  (0) 2019.05.31
v8 tracing 지원을 위한 작업 기록  (0) 2018.11.20
Comments