1 摘要
本篇文章是Builtin专题的第三篇,讲解Bytecode的执行、数据结构以及Dispatch。dispatchtable是连接Bytecode之间的纽带,它记录了每条Bytecode handler的地址,Ignition通过dispatchtable查找并执行相应的Bytecode。本文内容组织方法:Bytecode的执行和数据结构(章节2);Bytecode的调度(章节3)。
2 Bytecode的执行
在V8中,负责执行Bytecode的解释器是Ignition,Ignition执行Bytecode时要做很多复杂的准备工作,这些“准备工作”后续文章讲解,我们重点说明Bytecode的执行。
Bytecode以JavaScript函数为粒度生成并存储在Bytecode array中,即Bytecode array是存储Bytecode的数组,源码如下:
1. class BytecodeArray : public FixedArrayBase {
2. public:
3. static constexpr int SizeFor(int length) {
4. return OBJECT_POINTER_ALIGN(kHeaderSize + length);
5. }
6. inline byte get(int index) const;
7. inline void set(int index, byte value);
8. inline Address GetFirstBytecodeAddress();
9. //省略...................
10. };
我们仅说明与本文有关的两点内容:
(1) 第3行代码SizeFor(int length)
计算Bytecode array的长度,参数length
的值是编译JavaScript函数后得到的Bytecode的数量,length+Bytecode array需要的空间等于Bytecode array的长度。在创建Bytecode array时,使用SizeFor(int length)
计算申请内存的长度;
(2) 第8行代码GetFirstBytecodeAddress()
获取Bytecode的首地址。把Parser生成的Bytecode拷贝到Bytecode array时会用到该函数。
在Factory::NewBytecodeArray()
中,使用SizeFor(int length)
的返回值申请内存,用CopyBytes()
把Bytecode拷贝到首地址中。下面是一段Bytecode源码:
1. a7 StackCheck
2. 12 00 LdaConstant [0]
3. 15 01 00 StaGlobal [1], [0]
4. 13 01 02 LdaGlobal [1], [2]
5. 26 f9 Star r2
6. 29 f9 02 LdaNamedPropertyNoFeedback r2, [2]
7. 26 fa Star r1
8. 0c 05 LdaSmi [5]
9. Constant pool (size = 6)
10. 0000005EAE403019: [FixedArray] in OldSpace
11. - map: 0x03d0be000169 <Map>
12. - length: 6
13. 0: 0x005eae402f59 <String[#22]: ignoreCase here we go!>
14. 1: 0x038ee90c3cc1 <String[#1]: a>
15. 2: 0x01b92bc2bde1 <String[#9]: substring>
16. 3: 0x038ee90c3fa9 <String[#1]: b>
17. 4: 0x01b92bc33839 <String[#7]: console>
18. 5: 0x01b92bc32e79 <String[#3]: log>
第2行代码12 00 LdaConstant [0]
:12
是LdaConstant的编号,这个编号也是LdaConstant
的枚举值,即Bytecode[0x12]=kLdaConstant
,源码如下:
enum class Bytecode : uint8_t {
kWide, kExtraWide, kDebugBreakWide, kDebugBreakExtraWide, kDebugBreak0, kDebugBreak1, kDebugBreak2, kDebugBreak3, kDebugBreak4, kDebugBreak5, kDebugBreak6, kLdaZero, kLdaSmi, kLdaUndefined, kLdaNull, kLdaTheHole, kLdaTrue, kLdaFalse, kLdaConstant, kLdaGlobal, kLdaGlobalInsideTypeof, kStaGlobal, kPushContext, kPopContext, kLdaContextSlot, kLdaImmutableContextSlot, kLdaCurrentContextSlot, kLdaImmutableCurrentContextSlot, kStaContextSlot, kStaCurrentContextSlot, kLdaLookupSlot, kLdaLookupContextSlot, kLdaLookupGlobalSlot, kLdaLookupSlotInsideTypeof, kLdaLookupContextSlotInsideTypeof, kLdaLookupGlobalSlotInsideTypeof, kStaLookupSlot, kLdar, kStar, kMov, kLdaNamedProperty, kLdaNamedPropertyNoFeedback, kLdaKeyedProperty, kLdaModuleVariable, kStaModuleVariable, kStaNamedProperty, kStaNamedPropertyNoFeedback, kStaNamedOwnProperty, kStaKeyedProperty, kStaInArrayLiteral, kStaDataPropertyInLiteral, kCollectTypeProfile, kAdd, kSub, kMul, kDiv, kMod, kExp, kBitwiseOr, kBitwiseXor, kBitwiseAnd, kShiftLeft, kShiftRight, kShiftRightLogical, kAddSmi, kSubSmi, kMulSmi, kDivSmi, kModSmi, kExpSmi, kBitwiseOrSmi, kBitwiseXorSmi, kBitwiseAndSmi, kShiftLeftSmi, kShiftRightSmi, kShiftRightLogicalSmi, kInc, kDec, kNegate, kBitwiseNot, kToBooleanLogicalNot, kLogicalNot, kTypeOf, kDeletePropertyStrict, //省略...................
}
V8规定:fb代表寄存器R0,fa代表寄存器R1,以此类推。在29 f9 02 LdaNamedPropertyNoFeedback r2, [2]
中,f9
代表寄存器R2,02
代表常量池[2]。执行LdaNamedPropertyNoFeedback
时,Ignition通过Isolate获取dispatch_table的base address,再通过base address+0x29
得到LdaNamedPropertyNoFeedback
的handler,源码如下:
// Calls the GetProperty builtin for <object> and the key in the accumulator.
IGNITION_HANDLER(LdaNamedPropertyNoFeedback, InterpreterAssembler) {
TNode<Object> object = LoadRegisterAtOperandIndex(0);
TNode<Name> name = CAST(LoadConstantPoolEntryAtOperandIndex(1));
TNode<Context> context = GetContext();
TNode<Object> result =
CallBuiltin(Builtins::kGetProperty, context, object, name);
SetAccumulator(result);
Dispatch();
}
3 Dispatch
Dispatch_table
是指针数组,Bytecode的枚举值代表它在数组中的位置,该位置存储了对应的Bytecode handler的地址。Dispatch_table
的初始化如下:
1. void Interpreter::Initialize() {
2. Builtins* builtins = isolate_->builtins();
3. // Set the interpreter entry trampoline entry point now that builtins are
4. // initialized.
5. Handle<Code> code = BUILTIN_CODE(isolate_, InterpreterEntryTrampoline);
6. DCHECK(builtins->is_initialized());
7. DCHECK(code->is_off_heap_trampoline() ||
8. isolate_->heap()->IsImmovable(*code));
9. interpreter_entry_trampoline_instruction_start_ = code->InstructionStart();
10. // Initialize the dispatch table.
11. Code illegal = builtins->builtin(Builtins::kIllegalHandler);
12. int builtin_id = Builtins::kFirstBytecodeHandler;
13. ForEachBytecode([=, &builtin_id](Bytecode bytecode,
14. OperandScale operand_scale) {
15. Code handler = illegal;
16. if (Bytecodes::BytecodeHasHandler(bytecode, operand_scale)) {
17. #ifdef DEBUG
18. std::string builtin_name(Builtins::name(builtin_id));
19. std::string expected_name =
20. Bytecodes::ToString(bytecode, operand_scale, "") + "Handler";
21. DCHECK_EQ(expected_name, builtin_name);
22. #endif
23. handler = builtins->builtin(builtin_id++);
24. }
25. SetBytecodeHandler(bytecode, operand_scale, handler);
26. });
27. DCHECK(builtin_id == Builtins::builtin_count);
28. DCHECK(IsDispatchTableInitialized());
29. }
上述13-26行代码是匿名函数,其中25行代码初始化Dispatch_table
,源码如下:
1. void Interpreter::SetBytecodeHandler(Bytecode bytecode,
2. OperandScale operand_scale, Code handler) {
3. DCHECK(handler.kind() == Code::BYTECODE_HANDLER);
4. size_t index = GetDispatchTableIndex(bytecode, operand_scale);
5. dispatch_table_[index] = handler.InstructionStart();
6. }
7. //.........分隔线.............................................
8. size_t Interpreter::GetDispatchTableIndex(Bytecode bytecode,
9. OperandScale operand_scale) {
10. static const size_t kEntriesPerOperandScale = 1u << kBitsPerByte;
11. size_t index = static_cast<size_t>(bytecode);
12. return index + BytecodeOperands::OperandScaleAsIndex(operand_scale) *
13. kEntriesPerOperandScale;
14. }
上述第5行代码dispatch_table_
就是我们念念已久的存储dispatch table的成员变量;第4行代码GetDispatchTableIndex()
计算Bytecode handler在dispatch_table中的位置,这个位置与enum class Bytecode
是相同的。图1给出了SetBytecodeHandler
的调用堆栈。
Interpreter
的源码如下:
1. class Interpreter {
2. //............省略..................
3. private:
4. // Get dispatch table index of bytecode.
5. static size_t GetDispatchTableIndex(Bytecode bytecode,
6. OperandScale operand_scale);
7. static const int kNumberOfWideVariants = BytecodeOperands::kOperandScaleCount;
8. static const int kDispatchTableSize = kNumberOfWideVariants * (kMaxUInt8 + 1);
9. static const int kNumberOfBytecodes = static_cast<int>(Bytecode::kLast) + 1;
10. Isolate* isolate_;
11. Address dispatch_table_[kDispatchTableSize];
12. std::unique_ptr<uintptr_t[]> bytecode_dispatch_counters_table_;
13. Address interpreter_entry_trampoline_instruction_start_;
14. DISALLOW_COPY_AND_ASSIGN(Interpreter);
15. };
上述第11行代码dispatch_table_
是Interpreter
的成员变量。Interpreter
是Isolate的成员变量,源码如下:
1. class Isolate final : private HiddenFactory {
2. //省略...............
3. const AstStringConstants* ast_string_constants_ = nullptr;
4. interpreter::Interpreter* interpreter_ = nullptr;
5. compiler::PerIsolateCompilerCache* compiler_cache_ = nullptr;
6. Zone* compiler_zone_ = nullptr;
7. CompilerDispatcher* compiler_dispatcher_ = nullptr;
8. friend class heap::HeapTester;
9. friend class TestSerializer;
10. DISALLOW_COPY_AND_ASSIGN(Isolate);
11. };
通过上述代码可以看出:Isolate->interpreter_->dispatch_table_
获取dispatch_table_
。
下面是在Bytecode handler中调用的Dispatch()
的源码:
1. void InterpreterAssembler::Dispatch() {
2. Comment("========= Dispatch");
3. DCHECK_IMPLIES(Bytecodes::MakesCallAlongCriticalPath(bytecode_), made_call_);
4. TNode<IntPtrT> target_offset = Advance();
5. TNode<WordT> target_bytecode = LoadBytecode(target_offset);
6. if (Bytecodes::IsStarLookahead(bytecode_, operand_scale_)) {
7. target_bytecode = StarDispatchLookahead(target_bytecode);
8. }
9. DispatchToBytecode(target_bytecode, BytecodeOffset());
10. }
11. //......分隔线....................................
12. void InterpreterAssembler::DispatchToBytecode(
13. TNode<WordT> target_bytecode, TNode<IntPtrT> new_bytecode_offset) {
14. if (FLAG_trace_ignition_dispatches) {
15. TraceBytecodeDispatch(target_bytecode);
16. }
17. TNode<RawPtrT> target_code_entry = Load<RawPtrT>(
18. DispatchTablePointer(), TimesSystemPointerSize(target_bytecode));
19. DispatchToBytecodeHandlerEntry(target_code_entry, new_bytecode_offset);
20. }
21. //...........分隔线....................................
22. void InterpreterAssembler::DispatchToBytecodeHandlerEntry(
23. TNode<RawPtrT> handler_entry, TNode<IntPtrT> bytecode_offset) {
24. // Propagate speculation poisoning.
25. TNode<RawPtrT> poisoned_handler_entry =
26. UncheckedCast<RawPtrT>(WordPoisonOnSpeculation(handler_entry));
27. TailCallBytecodeDispatch(InterpreterDispatchDescriptor{},
28. poisoned_handler_entry, GetAccumulatorUnchecked(),
29. bytecode_offset, BytecodeArrayTaggedPointer(),
30. DispatchTablePointer());
31. }
32. //...........分隔线....................................
33. void CodeAssembler::TailCallBytecodeDispatch(
34. const CallInterfaceDescriptor& descriptor, TNode<RawPtrT> target,
35. TArgs... args) {
36. DCHECK_EQ(descriptor.GetParameterCount(), sizeof...(args));
37. auto call_descriptor = Linkage::GetBytecodeDispatchCallDescriptor(
38. zone(), descriptor, descriptor.GetStackParameterCount());
39. Node* nodes[] = {target, args...};
40. CHECK_EQ(descriptor.GetParameterCount() + 1, arraysize(nodes));
41. raw_assembler()->TailCallN(call_descriptor, arraysize(nodes), nodes);
42. }
上述三个方法共同实现Bytecode的dispatch。第5行代码计算target_bytecode;第17行代码计算target_bytecode_entry;第27行代码开始跳转;第34行代码创建call discriptor
;第41行代码生成Node节点,并把该节点添加到当前基本块的尾部,至此跳转完成。TailCallN()
的详细讲解参见第十一篇文章。图2给出了Dispatch()
的调用堆栈。
技术总结
(1) Bytecode的编号是Bytecode handler在数组dispatch_table_
中的下标;
(2) dispatchtable的初始化在Isolate启动时完成;
(3) 使用固定的物理寄存器保存dispatch_table_
的优点是:避免不必要的入栈和出栈,简化Bytecode的设计,提高了Dispatch的效率;
提示:我调试V8时,dispatch_table_
始终保存在物理寄存器R15中,调试方法参见第18篇文章。
好了,今天到这里,下次见。
个人能力有限,有不足与纰漏,欢迎批评指正
微信:qq9123013 备注:v8交流 邮箱:v8blink@outlook.com