CallBuiltin()調用過程詳解
01、摘要
本篇文章是Builtin專題的第五篇,詳細分析Builtin的調用過程。在Bytecode handler中使用CallBuiltin()調用Builtin是最常見的情況,本文將詳解CallBuiltin()源碼和相關的數據結構。本文內容組織方式:重要數據結構(章節2);CallBuiltin()源碼(章節3)。
02、數據結構
提示:Just-In-Time Compiler是本文的前導知識,請讀者自行查閱。
Builtin的調用過程主要分為兩部分:查詢Builtin表找到相應的入口函數、計算calldescriptor。下面解釋相關的數據結構:
(1) Builtin名字(例如Builtin::kStoreGlobalIC),名字是枚舉類型變量,CallBuiltin()使用該名字查詢Builtin表,找到相應的入口函數,源碼如下:
class Builtins {
//...............省略....................
enum Name : int32_t {
#define DEF_ENUM(Name, ...) k##Name,
BUILTIN_LIST(DEF_ENUM, DEF_ENUM, DEF_ENUM, DEF_ENUM, DEF_ENUM, DEF_ENUM,
DEF_ENUM)
#undef DEF_ENUM
builtin_count,
}
展開之后如下:
enum Name:int32_t{kRecordWrite, kEphemeronKeyBarrier, kAdaptorWithBuiltinExitFrame, kArgumentsAdaptorTrampoline,......}
(2) Builtin表存儲Builtin的地址。Builtin表的位置是isoate->isolatedata->builtins_,源碼如下:
class IsolateData final {
public:
explicit IsolateData(Isolate* isolate) : stack_guard_(isolate) {}
//............省略....................
Address* builtins() { return builtins_; }
}
builtins_是Address類型的數組,與enum Name:int32_t{}配合使用可查詢對應的Builtin地址(下面的第2行代碼就完成了地址查詢),源碼如下:
1.Callable Builtins::CallableFor(Isolate* isolate, Name name) {
2. Handle code = isolate->builtins()->builtin_handle(name);
3. return Callable{code, CallInterfaceDescriptorFor(name)};
4.}
上述代碼第3行CallInterfaceDescriptorFor返回Builtin的調用信息,該信息與code共同組成了Callable。
(3) Code類,該類包括Builtin地址、指令的開始和結束以及填充信息,它的作用之一是創建snapshot文件,源碼如下:
1. class Code : public HeapObject {
2. public:
3. #define CODE_KIND_LIST(V) \
4. V(OPTIMIZED_FUNCTION) V(BYTECODE_HANDLER) \
5. V(STUB) V(BUILTIN) V(REGEXP) V(WASM_FUNCTION) V(WASM_TO_CAPI_FUNCTION) \
6. V(WASM_TO_JS_FUNCTION) V(JS_TO_WASM_FUNCTION) V(JS_TO_JS_FUNCTION) \
7. V(WASM_INTERPRETER_ENTRY) V(C_WASM_ENTRY)
8. inline int builtin_index() const;
9. inline int handler_table_offset() const;
10. inline void set_handler_table_offset(int offset);
11. // The body of all code objects has the following layout.
12. // +--------------------------+ <-- raw_instruction_start()
13. // | instructions |
14. // | ... |
15. // +--------------------------+
16. // | embedded metadata | <-- safepoint_table_offset()
17. // | ... | <-- handler_table_offset()
18. // | | <-- constant_pool_offset()
19. // | | <-- code_comments_offset()
20. // | |
21. // +--------------------------+ <-- raw_instruction_end()
22. // If has_unwinding_info() is false, raw_instruction_end() points to the first
23. // memory location after the end of the code object. Otherwise, the body
24. // continues as follows:
25. // +--------------------------+
26. // | padding to the next |
27. // | 8-byte aligned address |
28. // +--------------------------+ <-- raw_instruction_end()
29. // | [unwinding_info_size] |
30. // | as uint64_t |
31. // +--------------------------+ <-- unwinding_info_start()
32. // | unwinding info |
33. // | ... |
34. // +--------------------------+ <-- unwinding_info_end()
35. // and unwinding_info_end() points to the first memory location after the end
36. // of the code object.
37. };
上述代碼第3-7行說明了當前Code是哪種指令類型;第9-10代碼是異常處理程序;第11-36行注釋說明了Code的內存布局。寫snapshot文件時內存布局會有細微變化,詳情請參考mksnapshot.exe源碼。
(4) CallInterfaceDescriptor描述了Builtin入口函數的寄存器參數、堆棧參數和返回值等信息,調用Builtin時會使用這些信息,源碼如下:
1. class V8_EXPORT_PRIVATE CallInterfaceDescriptor {
2. public:
3. Flags flags() const { return data()->flags(); }
4. bool HasContextParameter() const {return (flags() & CallInterfaceDescriptorData::kNoContext) == 0;}
5. int GetReturnCount() const { return data()->return_count(); }
6. MachineType GetReturnType(int index) const {return data()->return_type(index);}
7. int GetParameterCount() const { return data()->param_count(); }
8. int GetRegisterParameterCount() const {return data()->register_param_count();}
9. int GetStackParameterCount() const {return data()->param_count() - data()->register_param_count();}
10. Register GetRegisterParameter(int index) const {return data()->register_param(index);}
11. MachineType GetParameterType(int index) const {return data()->param_type(index);}
12. RegList allocatable_registers() const {return data()->allocatable_registers();}
13. //..............省略...................
14. private:
15. const CallInterfaceDescriptorData* data_;
16. }
上述代碼第5行是Builtin的返回值數量;第6行是返回值的類型;第7行是參數數量;第8代是寄存器參數的數量;第9行是棧參數的數量;第10-12行是獲取參數;第15行代碼CallInterfaceDescriptorData存儲上述代碼中所需的信息,即返回值數量、類型等信息,源碼如下:
1. class V8_EXPORT_PRIVATE CallInterfaceDescriptorData {
2. private:
3. bool IsInitializedPlatformSpecific() const {
4. //.........省略..............
5. }
6. bool IsInitializedPlatformIndependent() const {
7. //.........省略..............
8. }
9. int register_param_count_ = -1;
10. int return_count_ = -1;
11. int param_count_ = -1;
12. Flags flags_ = kNoFlags;
13. RegList allocatable_registers_ = 0;
14. Register* register_params_ = nullptr;
15. MachineType* machine_types_ = nullptr;
16. DISALLOW_COPY_AND_ASSIGN(CallInterfaceDescriptorData);
17. };
上述代碼第9-15行定義的變量就是CallInterfaceDescriptor中提到的返回值、參數等信息。
以上內容是CallBuiltin()使用的主要數據結構。
03、CallBuiltin()
來看下面的使用場景:
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();
}
LdaNamedPropertyNoFeedback的作用是獲取屬性,例如從document屬性中獲取getelementbyID方法,該方法的獲取由CallBuiltin調用Builtins::kGetProperty實現,源碼如下:
template <class... TArgs>
TNode CallBuiltin(Builtins::Name id, SloppyTNode context, TArgs... args) {
return CallStub(Builtins::CallableFor(isolate(), id), context, args...);
}
上述代碼中id代表Builtin的名字,即前面提到的枚舉值;args有兩個成員:args[0]代表object(上述例子中的document),args[1]代表name(getelementbyID方法)。CallStub()源碼如下:
1. template <class T = Object, class... TArgs>
2. TNode<T> CallStub(Callable const& callable, SloppyTNode<Object> context,
3. TArgs... args) {
4. TNode<Code> target = HeapConstant(callable.code());
5. return CallStub<T>(callable.descriptor(), target, context, args...);
6. }
7. //..............分隔線..................
8. template <class T = Object, class... TArgs>
9. TNode<T> CallStub(const CallInterfaceDescriptor& descriptor,
10. SloppyTNode<Code> target, SloppyTNode<Object> context,
11. TArgs... args) {
12. return UncheckedCast<T>(CallStubR(StubCallMode::kCallCodeObject, descriptor,
13. 1, target, context, args...));
14. }
15. //..............分隔線..................
16. template <class... TArgs>
17. Node* CallStubR(StubCallMode call_mode,
18. const CallInterfaceDescriptor& descriptor, size_t result_size,
19. SloppyTNode<Object> target, SloppyTNode<Object> context,
20. TArgs... args) {
21. return CallStubRImpl(call_mode, descriptor, result_size, target, context,
22. {args...});
23. }
上述代碼第4行創建target對象,該對象是Builtin的入口地址;第5行代碼調用CallStub()方法(第9行),最終進入CallStubR()。在CallStubR()中調用CallStubRImpl(),源碼如下:
1. Node* CodeAssembler::CallStubRImpl( ) {
2. DCHECK(call_mode == StubCallMode::kCallCodeObject ||
3. call_mode == StubCallMode::kCallBuiltinPointer);
4. constexpr size_t kMaxNumArgs = 10;
5. DCHECK_GE(kMaxNumArgs, args.size());
6. NodeArray<kMaxNumArgs + 2> inputs;
7. inputs.Add(target);
8. for (auto arg : args) inputs.Add(arg);
9. if (descriptor.HasContextParameter()) {
10. inputs.Add(context);
11. }
12. return CallStubN(call_mode, descriptor, result_size, inputs.size(),
13. inputs.data());
14. }
上述代碼第7-10行將所有參數添加到數組inputs中,inputs內容依次為:Builtin的入口地址(code類型)、object、name、context。進入第12行代碼,CallStubN()源碼如下:
1. Node* CodeAssembler::CallStubN() {2. // implicit nodes are target and optionally context.3. int implicit_nodes = descriptor.HasContextParameter() ? 2 : 1;4. int argc = input_count - implicit_nodes;5. // Extra arguments not mentioned in the descriptor are passed on the stack.6. int stack_parameter_count = argc - descriptor.GetRegisterParameterCount();7. auto call_descriptor = Linkage::GetStubCallDescriptor(8. zone(), descriptor, stack_parameter_count, CallDescriptor::kNoFlags,9. Operator::kNoProperties, call_mode);10. CallPrologue();11. Node* return_value =12. raw_assembler()->CallN(call_descriptor, input_count, inputs);13. HandleException(return_value);14. CallEpilogue();15. return return_value;16. }
上述第7行代碼call_descriptor的返回值類型如下:
CallDescriptor( // --
kind, // kind
target_type, // target MachineType
target_loc, // target location
locations.Build(), // location_sig
stack_parameter_count, // stack_parameter_count
properties, // properties
kNoCalleeSaved, // callee-saved registers
kNoCalleeSaved, // callee-saved fp
CallDescriptor::kCanUseRoots | flags, // flags
descriptor.DebugName(), // debug name
descriptor.allocatable_registers())
上述信息為調用Builtin做準備工作。CallStubN()中第11行代碼:完成Builtin的調用,第13行代碼:異常處理。圖1給出了CodeAssembler()的調用堆棧,此時正在建立Builtin,Builtin的建立發生在Isolate初始化階段。
技術總結
(1) Builtin名字與Builtin的入口之間存在一一對應的關系,這種關系由isoate->isolatedata->builtins表示,builtins是在Isolate初始化過程中創建的Address數組;
(2) inputs數組除了包括參數之外還有target和context;
(3) Builtin的參數、返回值等信息的詳細說明可在BUILTIN_LIST宏中查看。
好了,今天到這里,下次見。