visit
Welcome to other chapters of Let’s Understand Chrome V8
Figure 1 shows the interpreter of bytecodes. The figure is detailed enough for JavaScript developers. But for V8 learners, Figure 1 lacks a lot of detail, such as: the ignition startup, loading the bytecode, and dispatch. Let’s debug bytecode to dive into those details.
Note: Before debugging, you’d better understand stack frames, the encoding of bytecodes and registers, see here.
1. V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
2. const InvokeParams& params) {
3. if (params.target->IsJSFunction()) {
4. //............omit...............
5. }
6. Object value;
7. Handle<Code> code =
8. JSEntry(isolate, params.execution_target, params.is_construct);
9. {
10. SaveContext save(isolate);
11. SealHandleScope shs(isolate);
12. if (FLAG_clear_exceptions_on_js_entry) isolate->clear_pending_exception();
13. if (params.execution_target == Execution::Target::kCallable) {
14. using JSEntryFunction = GeneratedCode<Address(
15. Address root_register_value, Address new_target, Address target,
16. Address receiver, intptr_t argc, Address** argv)>;
17. // clang-format on
18. JSEntryFunction stub_entry =
19. JSEntryFunction::FromAddress(isolate, code->InstructionStart());
20. Address orig_func = params.new_target->ptr();
21. Address func = params.target->ptr();
22. Address recv = params.receiver->ptr();
23. Address** argv = reinterpret_cast<Address**>(params.argv);
24. RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);
25. value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
26. orig_func, func, recv, params.argc, argv));
27. } else {
28. //............omit...............
29. }
30. }
31. //............omit...............
32. return Handle<Object>(value, isolate);
33. }
(1) Line 7, code, it is the Builtin::JSEntry pointer, now the value is 1FA 0E06 ED30 (In my debug context).
(2) Line 18, stub_entry, it is the Ignition’s entrance. Below is the function that gets stub_entry from Builtin::JSEntry.
Address Code::OffHeapInstructionStart() const {
DCHECK(is_off_heap_trampoline());
if (Isolate::CurrentEmbeddedBlob() == nullptr) return raw_instruction_start();
EmbeddedData d = EmbeddedData::FromBlob();
return d.InstructionStartOfBuiltin(builtin_index());
}
(3) Line21, func, it is the address of JSFunction, namely your JavaScript code. The value is 16 2BD8 15A9.
(4) The address of Builtin::InterpreterEntryTrampoline is 52 61C0 8A41.
(5) The dispatch_table is 1FA 0E08 CFB0.
Figure 2 is the call stack that is going into bytecode.
In Figure 3, the register RCX is 1FA 1326 1840(stub_entry). As I mentioned, the stub_entry is the entrance of Ignition.
In Figure 4, the register RBX is 1FA0E068A00 which is the first argument of stub_entry.Call(), that is isolate->isolate_data()->isolate_root().
Figure 5 is moving the R8 and calling RSI. The register R8 is func, and the RSI is stub_entry, below is the function to which the stub_entry is pointing.
1. void Builtins::Generate_JSEntry(MacroAssembler* masm) {
2. Generate_JSEntryVariant(masm, StackFrame::ENTRY,
3. Builtins::kJSEntryTrampoline);
4. }
5. //==================separation===========================
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. {
11. NoRootArrayScope uninitialized_root_register(masm);
12. __ pushq(rbp);
13. __ movq(rbp, rsp);
14. __ Push(Immediate(StackFrame::TypeToMarker(type)));
15. __ AllocateStackSpace(kSystemPointerSize);
16. __ pushq(r12);
17. __ pushq(r13);
18. __ pushq(r14);
19. __ pushq(r15);
20. #ifdef _WIN64
21. __ pushq(rdi); // Only callee save in Win64 ABI, argument in AMD64 ABI.
22. __ pushq(rsi); // Only callee save in Win64 ABI, argument in AMD64 ABI.
23. #endif
24. __ pushq(rbx);
25. #ifdef _WIN64 //Here is True, my OS is Win10!!!!!!!!!!!!!!!!!!
26. // On Win64 XMM6-XMM15 are callee-save.
27. __ AllocateStackSpace(EntryFrameConstants::kXMMRegistersBlockSize);
28. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 0), xmm6);
29. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 1), xmm7);
30. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 2), xmm8);
31. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 3), xmm9);
32. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 4), xmm10);
33. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 5), xmm11);
34. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 6), xmm12);
35. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 7), xmm13);
36. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 8), xmm14);
37. __ movdqu(Operand(rsp, EntryFrameConstants::kXMMRegisterSize * 9), xmm15);
38. STATIC_ASSERT(EntryFrameConstants::kCalleeSaveXMMRegisters == 10);
39. STATIC_ASSERT(EntryFrameConstants::kXMMRegistersBlockSize ==
40. EntryFrameConstants::kXMMRegisterSize *
41. EntryFrameConstants::kCalleeSaveXMMRegisters);
42. #endif
43. //............omit...........................
44. }
Builtin::InterpreterEntryTrampoline lookups at the dispatch_table to find the target bytecode and call it, shown in Figure 7.
Let’s examine dispatch in a little more depth, see Figure 7.
1. IGNITION_HANDLER(LdaConstant, InterpreterAssembler) {
2. TNode<Object> constant = LoadConstantPoolEntryAtOperandIndex(0);
3. SetAccumulator(constant);
4. Dispatch();
5. }
At the assembly level, we can see the Ignition startup and bytecode execution, which can help us understand the V8 interpreter better.
Okay, that wraps it up for this share. I’ll see you guys next time, take care!
Please reach out to me if you have any issues. WeChat: qq9123013 Email: