Глава 30. Загрузка JS-функции и выполнение байт-кода
Добро пожаловать в другие главы Давайте разберемся с Chrome V8
В этой статье я расскажу о зажигании и дам вам представление о том, как оно работает, как оно загружает JS-функцию и что она будет делать перед выполнением.
1. Вызов JS-функции
Для интерпретатора V8 все это начинается с загрузки JS-функции (термин V8 — invoke), поскольку он имеет массив байт-кода. Ниже приведена функция вызова.
1. InvokeParams InvokeParams::SetUpForCall(Isolate* isolate, | |
2. Handle<Object> callable, | |
3. Handle<Object> receiver, int argc, | |
4. Handle<Object>* argv) { | |
5. InvokeParams params; | |
6. params.target = callable; | |
7. params.receiver = NormalizeReceiver(isolate, receiver); | |
8. params.argc = argc; | |
9. params.argv = argv; | |
10. params.new_target = isolate->factory()->undefined_value(); | |
11. //omit................... | |
12. return params; | |
13. } | |
14. //...............分隔线............................ | |
15. V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate, | |
16. const InvokeParams& params) { | |
17. Handle<Code> code = | |
18. JSEntry(isolate, params.execution_target, params.is_construct); | |
19. { | |
20. if (params.execution_target == Execution::Target::kCallable) { | |
21. using JSEntryFunction = GeneratedCode<Address( | |
22. Address root_register_value, Address new_target, Address target, | |
23. Address receiver, intptr_t argc, Address** argv)>; | |
24. JSEntryFunction stub_entry = | |
25. JSEntryFunction::FromAddress(isolate, code->InstructionStart()); | |
26. Address orig_func = params.new_target->ptr(); | |
27. Address func = params.target->ptr(); | |
28. Address recv = params.receiver->ptr(); | |
29. Address** argv = reinterpret_cast<Address**>(params.argv); | |
30. RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution); | |
31. value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(), | |
32. orig_func, func, recv, params.argc, argv)); | |
33. } else { | |
34. } | |
35. } | |
36. } |
Перед вызовом JS-функции V8 оборачивает ее в invoke-params, но не добавляет другие новые вещи, поэтому нас это не волнует, но мы помним, какой член является в ней JSfcuntion. Я выкладываю важное содержание ниже.
(1) Строки 1–13, SetupForCall() — это функция-оболочка, о которой я упоминал.
(2) Строка 17, код представляет собой адрес функции Builtin::kJSEntry.
(3) Строка 24, stub_entry — это первая инструкция Builtin::kJSEntry.
(4) Строка 27, func — это JS-функция, которая будет выполняться.
(5) Строка 31, stub_entry.Call начинает переходить в Ignition для выполнения вашего JavaScript. Примечание.ваш код JavaScript еще не вызван, так как Ignition создает стек вызовов, а затем переходит к вашему коду.
2. Встроенный::kJSEntry
Прежде чем говорить о Builtin::kJSEntry, я хочу подчеркнуть некоторые члены JSFunction, которые часто используются в Ignition.
(1) SharedFunction является членом JSFunction, важно знать, что SharedFunction имеет массив байт-кода, который будет выполнять Ignition.
(2) код – это встроенный модуль::kInterpreterEntryTrampoline.
(3) контекст — это контекст, соответствующий текущей среде выполнения.
Перейдем к файлу Builtin::kJSEntry.
1. void Builtins::Generate_JSEntry(MacroAssembler* masm) { | |
2. Generate_JSEntryVariant(masm, StackFrame::ENTRY, | |
3. Builtins::kJSEntryTrampoline); | |
4. } | |
5. //............................ | |
6. void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type, | |
7. Builtins::Name entry_trampoline) { | |
8. Label invoke, handler_entry, exit; | |
9. Label not_outermost_js, not_outermost_js_2; | |
10. { // NOLINT. Scope block confuses linter. | |
11. NoRootArrayScope uninitialized_root_register(masm); | |
12. //..............omit........... | |
13. // Initialize the root register. | |
14. // C calling convention. The first argument is passed in arg_reg_1. | |
15. __ movq(kRootRegister, arg_reg_1); | |
16. // Save copies of the top frame descriptor on the stack. | |
17. ExternalReference c_entry_fp = ExternalReference::Create( | |
18. IsolateAddressId::kCEntryFPAddress, masm->isolate()); | |
19. { | |
20. Operand c_entry_fp_operand = masm->ExternalReferenceAsOperand(c_entry_fp); | |
21. __ Push(c_entry_fp_operand); | |
22. } | |
23. // Store the context address in the previously-reserved slot. | |
24. ExternalReference context_address = ExternalReference::Create( | |
25. IsolateAddressId::kContextAddress, masm->isolate()); | |
26. __ Load(kScratchRegister, context_address); | |
27. static constexpr int kOffsetToContextSlot = -2 * kSystemPointerSize; | |
28. __ movq(Operand(rbp, kOffsetToContextSlot), kScratchRegister); | |
29. __ jmp(&invoke); | |
30. __ bind(&handler_entry); | |
31. // Store the current pc as the handler offset. It's used later to create the | |
32. // handler table. | |
33. masm->isolate()->builtins()->SetJSEntryHandlerOffset(handler_entry.pos()); | |
34. // Caught exception: Store result (exception) in the pending exception | |
35. // field in the JSEnv and return a failure sentinel. | |
36. ExternalReference pending_exception = ExternalReference::Create( | |
37. IsolateAddressId::kPendingExceptionAddress, masm->isolate()); | |
38. __ Store(pending_exception, rax); | |
39. __ LoadRoot(rax, RootIndex::kException); | |
40. __ jmp(&exit); | |
41. // Invoke: Link this frame into the handler chain. | |
42. __ bind(&invoke); | |
43. __ PushStackHandler(); | |
44. // Invoke the function by calling through JS entry trampoline builtin and | |
45. // pop the faked function when we return. | |
46. Handle<Code> trampoline_code = | |
47. masm->isolate()->builtins()->builtin_handle(entry_trampoline); | |
48. __ Call(trampoline_code, RelocInfo::CODE_TARGET); | |
49. //.............omit............ | |
50. } |
В приведенном выше коде строка 2 вызывает Generate_JSEntryVariant(), третий параметр которой — это батут, который в конечном итоге вызывает наш байт-код.
В строке 15 arg_reg_1 представляет собой isolate-›isolate_data()-›isolate_root(), причина, по которой я подчеркиваю это, заключается в том, что isolate_root() содержит все необходимые вещи, такие как встроенные модули и куча, которые полезны во время выполнения.
Строки 17–22, поместите верхний фрейм в стек, как мы это делаем в X86.
Строки 24–26 загружают текущий контекст в ScratchRegister.
Строки 46–48, выполните JSEntryTrampoline.
3. Встроенный::kJSEntryTrampoline
1. void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) { | |
2. Generate_JSEntryTrampolineHelper(masm, false); | |
3. } | |
4. //.............分隔线.................. | |
5. static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, | |
6. bool is_construct) { | |
7. // Expects six C++ function parameters. | |
8. // - Address root_register_value | |
9. // - Address new_target (tagged Object pointer) | |
10. // - Address function (tagged JSFunction pointer) | |
11. // - Address receiver (tagged Object pointer) | |
12. // - intptr_t argc | |
13. // - Address** argv (pointer to array of tagged Object pointers) | |
14. // (see Handle::Invoke in execution.cc). | |
15. // Open a C++ scope for the FrameScope. | |
16. { | |
17. // Platform specific argument handling. After this, the stack contains | |
18. // an internal frame and the pushed function and receiver, and | |
19. // register rax and rbx holds the argument count and argument array, | |
20. // while rdi holds the function pointer, rsi the context, and rdx the | |
21. // new.target. | |
22. // MSVC parameters in: | |
23. // rcx : root_register_value | |
24. // rdx : new_target | |
25. // r8 : function | |
26. // r9 : receiver | |
27. // [rsp+0x20] : argc | |
28. // [rsp+0x28] : argv | |
29. __ movq(rdi, arg_reg_3); | |
30. __ Move(rdx, arg_reg_2); | |
31. // rdi : function | |
32. // rdx : new_target | |
33. // Setup the context (we need to use the caller context from the isolate). | |
34. ExternalReference context_address = ExternalReference::Create( | |
35. IsolateAddressId::kContextAddress, masm->isolate()); | |
36. __ movq(rsi, masm->ExternalReferenceAsOperand(context_address)); | |
37. // Push the function and the receiver onto the stack. | |
38. __ Push(rdi); | |
39. __ Push(arg_reg_4); | |
40. // Current stack contents: | |
41. // [rsp + 2 * kSystemPointerSize ... ] : Internal frame | |
42. // [rsp + kSystemPointerSize] : function | |
43. // [rsp] : receiver | |
44. // Current register contents: | |
45. // rax : argc | |
46. // rbx : argv | |
47. // rsi : context | |
48. // rdi : function | |
49. // rdx : new.target | |
50. __ bind(&enough_stack_space); | |
51. // Copy arguments to the stack in a loop. | |
52. // Invoke the builtin code. | |
53. Handle<Code> builtin = is_construct | |
54. ? BUILTIN_CODE(masm->isolate(), Construct) | |
55. : masm->isolate()->builtins()->Call(); | |
56. __ Call(builtin, RelocInfo::CODE_TARGET); | |
57. } | |
58. __ ret(0); | |
59. } |
В приведенном выше коде строки 7–28 помещают параметры в стек, в котором функция параметра представляет собой байт-код, полученный из нашего JavaScript. В строках 40–49 комментарии описывают структуру стекового фрейма. Строка 53, получите встроенный адрес, то есть Builtin::kCall_ReceiverIsAny.
4. Встроенный::kCall_ReceiverIsAny
1. void Builtins::Generate_Call_ReceiverIsAny(MacroAssembler* masm) { | |
2. Generate_Call(masm, ConvertReceiverMode::kAny); | |
3. } | |
4. //...........分隔线.................. | |
5. void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { | |
6. // ----------- S t a t e ------------- | |
7. // -- rax : the number of arguments (not including the receiver) | |
8. // -- rdi : the target to call (can be any Object) | |
9. // ----------------------------------- | |
10. StackArgumentsAccessor args(rsp, rax); | |
11. Label non_callable; | |
12. __ JumpIfSmi(rdi, &non_callable); | |
13. __ CmpObjectType(rdi, JS_FUNCTION_TYPE, rcx); | |
14. __ Jump(masm->isolate()->builtins()->CallFunction(mode), | |
15. RelocInfo::CODE_TARGET, equal); | |
16. __ CmpInstanceType(rcx, JS_BOUND_FUNCTION_TYPE); | |
17. __ Jump(BUILTIN_CODE(masm->isolate(), CallBoundFunction), | |
18. RelocInfo::CODE_TARGET, equal); | |
19. // Check if target has a [[Call]] internal method. | |
20. __ testb(FieldOperand(rcx, Map::kBitFieldOffset), | |
21. Immediate(Map::IsCallableBit::kMask)); | |
22. __ j(zero, &non_callable, Label::kNear); | |
23. // Check if target is a proxy and call CallProxy external builtin | |
24. __ CmpInstanceType(rcx, JS_PROXY_TYPE); | |
25. __ Jump(BUILTIN_CODE(masm->isolate(), CallProxy), RelocInfo::CODE_TARGET, | |
26. equal); | |
27. } |
В приведенном выше коде, строка 13, rdi — это выполняемая функция JSF, а rcx — ее карта. Как правило, CmpObjectType имеет значение true и переходит к строке 14 и в конечном итоге вызывает kCallFunction_ReceiverIsAny.
5. Встроенный::kCallFunction_ReceiverIsAny
1. void Builtins::Generate_CallFunction_ReceiverIsAny(MacroAssembler* masm) { | |
2. Generate_CallFunction(masm, ConvertReceiverMode::kAny); | |
3. } | |
4. //.............分隔线.................. | |
5. void Builtins::Generate_CallFunction(MacroAssembler* masm, | |
6. ConvertReceiverMode mode) { | |
7. StackArgumentsAccessor args(rsp, rax); | |
8. __ AssertFunction(rdi); | |
9. { | |
10. // ----------- S t a t e ------------- | |
11. // -- rax : the number of arguments (not including the receiver) | |
12. // -- rdx : the shared function info. | |
13. // -- rdi : the function to call (checked to be a JSFunction) | |
14. // -- rsi : the function context. | |
15. // ----------------------------------- | |
16. __ movzxwq( | |
17. rbx, FieldOperand(rdx, SharedFunctionInfo::kFormalParameterCountOffset)); | |
18. ParameterCount actual(rax); | |
19. ParameterCount expected(rbx); | |
20. __ InvokeFunctionCode(rdi, no_reg, expected, actual, JUMP_FUNCTION); | |
21. // The function is a "classConstructor", need to raise an exception. | |
22. __ bind(&class_constructor); | |
23. { | |
24. FrameScope frame(masm, StackFrame::INTERNAL); | |
25. __ Push(rdi); | |
26. __ CallRuntime(Runtime::kThrowConstructorNonCallableError); | |
27. } | |
28. } |
В приведенном выше коде строки 7–19 помещают параметры в стек, регистр rax имеет решающее значение для обеспечения того, чтобы аргументы JavaScript всегда совпадали, поскольку V8 заполняет Null, если аргумент отсутствует, или V8 отбрасывает лишнее. аргументы. Строки 10–14, rdi — это JSFuncion, а rdx — его SharedFunction. Завершите выполнение строки 20.
6. InvokeFunctionCode
1. void MacroAssembler::InvokeFunctionCode(Register function, Register new_target, | |
2. const ParameterCount& expected, | |
3. const ParameterCount& actual, | |
4. InvokeFlag flag) { | |
5. // On function call, call into the debugger if necessary. | |
6. CheckDebugHook(function, new_target, expected, actual); | |
7. // Clear the new.target register if not given. | |
8. if (!new_target.is_valid()) { | |
9. LoadRoot(rdx, RootIndex::kUndefinedValue); | |
10. } | |
11. Label done; | |
12. bool definitely_mismatches = false; | |
13. InvokePrologue(expected, actual, &done, &definitely_mismatches, flag, | |
14. Label::kNear); | |
15. if (!definitely_mismatches) { | |
16. // We call indirectly through the code field in the function to | |
17. // allow recompilation to take effect without changing any of the | |
18. // call sites. | |
19. static_assert(kJavaScriptCallCodeStartRegister == rcx, "ABI mismatch"); | |
20. LoadTaggedPointerField(rcx, | |
21. FieldOperand(function, JSFunction::kCodeOffset)); | |
22. if (flag == CALL_FUNCTION) { | |
23. CallCodeObject(rcx); | |
24. } else { | |
25. DCHECK(flag == JUMP_FUNCTION); | |
26. JumpCodeObject(rcx); | |
27. } | |
28. bind(&done); | |
29. } | |
30. } |
В приведенном выше коде строка 23 вызывает rcx, rcx — это просто Builtins::kInterpreterEntryTrampoline, который является прологом нашего JavaScript. Другими словами, если вам интересно, как выполняется ваш JavaScript, отладка отсюда — самый простой способ.
Следующее описание взято из v8.dev.
«Когда функция вызывается во время выполнения, вводится заглушка InterpreterEntryTrampoline. Эта заглушка устанавливает соответствующий кадр стека, а затем отправляет обработчику байт-кода интерпретатора первый байт-код функции, чтобы начать выполнение функции в интерпретаторе. Конец каждого обработчика байт-кода напрямую отправляет следующему обработчику через индекс в глобальной таблице интерпретатора на основе байт-кода».
Хорошо, на этом мы закончили. Увидимся в следующий раз, берегите себя!
Пожалуйста, свяжитесь со мной, если у вас есть какие-либо проблемы. WeChat: qq9123013 Электронная почта: v8blink@outlook.com
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord . Заинтересованы в хакинге роста? Ознакомьтесь с разделом Схема.