《Chrome V8源碼》32.字節碼和 Compiler Pipeline 的細節

fans news 發佈 2021-12-09T14:21:56+00:00

1 摘要本篇文章是 Builtin 專題的第七篇。

1 摘要

本篇文章是 Builtin 專題的第七篇。上篇文章講解了 Builtin::kInterpreterEntryTrampoline 源碼,本篇文章將介紹 Builin 的編譯過程,在此過程中可以看到 bytecode hanlder 生成 code 的技術細節,同時也可藉助此過程了解 Compiler Pipeline 技術和重要數據結構。

2 Bytecode handler的重要數據結構

GenerateBytecodeHandler()負責生成Bytecode hander,源碼如下:

1.  Handle<Code> GenerateBytecodeHandler(Isolate* isolate, const char* debug_name,
2.                                       Bytecode bytecode,
3.                                       OperandScale operand_scale,
4.                                       int builtin_index,
5.                                       const AssemblerOptions& options) {
6.    Zone zone(isolate->allocator(), ZONE_NAME);
7.    compiler::CodeAssemblerState state(
8.        isolate, &zone, InterpreterDispatchDescriptor{}, Code::BYTECODE_HANDLER,
9.        debug_name,
10.        FLAG_untrusted_code_mitigations
11.            ? PoisoningMitigationLevel::kPoisonCriticalOnly
12.            : PoisoningMitigationLevel::kDontPoison,
13.        builtin_index);
14.    switch (bytecode) {
15.  #define CALL_GENERATOR(Name, ...)                     \
16.    case Bytecode::k##Name:                             \
17.      Name##Assembler::Generate(&state, operand_scale); \
18.      break;
19.      BYTECODE_LIST(CALL_GENERATOR);
20.  #undef CALL_GENERATOR
21.    }
22.    Handle<Code> code = compiler::CodeAssembler::GenerateCode(&state, options);
23.  #ifdef ENABLE_DISASSEMBLER
24.    if (FLAG_trace_ignition_codegen) {
25.      StdoutStream os;
26.      code->Disassemble(Bytecodes::ToString(bytecode), os);
27.      os << std::flush;
28.    }
29.  #endif  // ENABLE_DISASSEMBLER
30.    return code;
31.  }

上述代碼第 7-13 行初始化 state,state 中包括 BytecodeOffset、 DispatchTable 和 Descriptor,Bytecode 編譯時會使用state。 第 14-21 行代碼生成 Bytecode handler 源碼。第 17 行 state 作為參數傳入 GenerateCode() 中,用於記錄 Bytecode hadler 的生成結果。下面以 LdaSmi 為例講解 Bytecode handler 的重要數據結構:

IGNITION_HANDLER(LdaSmi, InterpreterAssembler) {
  TNode<Smi> smi_int = BytecodeOperandImmSmi(0);
  SetAccumulator(smi_int);
  Dispatch();
}

上述代碼將累加寄存器的值設置為 smi。展開宏 IGNITION_HANDLER 後可以看到 LdaSmiAssembler 是子類,InterpreterAssembler 是父類,說明如下:

(1) LdaSmiAssembler 中包括生成 LdaSmi 的入口方法 Genrate(),源碼如下:

1.void Name##Assembler::Generate(compiler::CodeAssemblerState* state,
1.                               OperandScale scale) {
2.  Name##Assembler assembler(state, Bytecode::k##Name, scale);
3.  state->SetInitialDebugInformation(#Name, __FILE__, __LINE__);
4.  assembler.GenerateImpl();  
6.}

上述第3行代碼創建 LdaSmiAssembler 實例。第4行代碼把 debug 信息寫入state。

(2) InterpreterAssembler 提供解釋器相關的功能,源碼如下:

1.  class V8_EXPORT_private InterpreterAssembler : public CodeStubAssembler {
2.  public:
3.  //.............省略.........................
4.  private:
5.   TNode<BytecodeArray> BytecodeArrayTaggedPointer();
6.   TNode<ExternalReference> DispatchTablePointer();
7.   TNode<Object> GetAccumulatorUnchecked();
8.   TNode<RawPtrT> GetInterpretedFramePointer();
9.    compiler::TNode<IntPtrT> RegisterLocation(Register reg);
10.    compiler::TNode<IntPtrT> RegisterLocation(compiler::TNode<IntPtrT> reg_index);
11.    compiler::TNode<IntPtrT> NextRegister(compiler::TNode<IntPtrT> reg_index);
12.    compiler::TNode<Object> LoadRegister(compiler::TNode<IntPtrT> reg_index);
13.    void StoreRegister(compiler::TNode<Object> value,
14.                       compiler::TNode<IntPtrT> reg_index);
15.    void CallPrologue();
16.    void CallEpilogue();
17.    void TraceBytecodeDispatch(TNode<WordT> target_bytecode);
18.    void TraceBytecode(Runtime::FunctionId function_id);
19.    void Jump(compiler::TNode<IntPtrT> jump_offset, bool backward);
20.    void JumpConditional(compiler::TNode<BoolT> condition,
21.                         compiler::TNode<IntPtrT> jump_offset);
22.    void SaveBytecodeOffset();
23.    TNode<IntPtrT> ReloadBytecodeOffset();
24.    TNode<IntPtrT> Advance();
25.    TNode<IntPtrT> Advance(int delta);
26.    TNode<IntPtrT> Advance(TNode<IntPtrT> delta, bool backward = false);
27.    compiler::TNode<WordT> LoadBytecode(compiler::TNode<IntPtrT> bytecode_offset);
28.    void DispatchToBytecodeHandlerEntry(compiler::TNode<RawPtrT> handler_entry,
29.                                        compiler::TNode<IntPtrT> bytecode_offset);
30.    int CurrentBytecodeSize() const;
31.    OperandScale operand_scale() const { return operand_scale_; }
32.    Bytecode bytecode_;
33.    OperandScale operand_scale_;
34.    CodeStubAssembler::TVariable<RawPtrT> interpreted_frame_pointer_;
35.    CodeStubAssembler::TVariable<BytecodeArray> bytecode_array_;
36.    CodeStubAssembler::TVariable<IntPtrT> bytecode_offset_;
37.    CodeStubAssembler::TVariable<ExternalReference> dispatch_table_;
38.    CodeStubAssembler::TVariable<Object> accumulator_;
39.    AccumulatorUse accumulator_use_;
40.    bool made_call_;
41.    bool reloaded_frame_ptr_;
42.    bool bytecode_array_valid_;
43.    DISALLOW_COPY_AND_ASSIGN(InterpreterAssembler);
44.  };

上述第 5 行代碼獲取 BytecodeArray 的地址;第 6 行代碼獲取 DispatchTable 的地址;第 7 行代碼獲取累加寄存器的值;第8-13行代碼用於操作寄存器;第 15-16 行代碼用於調用函數前後的堆棧處理;第 17-18 行代碼用於跟蹤 Bytecode,其中第18行會調用Runtime::RuntimeInterpreterTraceBytecodeEntry以輸出寄存器信息;第 19-20 行代碼是兩條跳轉指令,在該指令的內部調用 Advance(第24-26行)來完成跳轉操作;第 24-26 行代碼用於獲取下一條 Bytecode;第 32-42 行代碼定義的成員變量在 Bytecode handler 中會被頻繁使用,例如在 SetAccumulator(zero_value) 中先設置 accumulator_use 為寫狀態,再把值寫入 accumulator_。

(3) CodeStubAssembler 是 InterpreterAssembler 的父類,提供 JavaScript 的特有方法,源碼如下:

1.  class V8_EXPORT_PRIVATE CodeStubAssembler: public compiler::CodeAssembler,
2.       public TorqueGeneratedExportedMacrosAssembler {
3.  public:
4.   TNode<Int32T> StringCharCodeAt(SloppyTNode<String> string,
5.                                  SloppyTNode<IntPtrT> index);
6.    TNode<String> StringFromSingleCharCode(TNode<Int32T> code);
7.    TNode<String> SubString(TNode<String> string, TNode<IntPtrT> from,
8.                            TNode<IntPtrT> to);
9.    TNode<String> StringAdd(Node* context, TNode<String> first,
10.                            TNode<String> second);
11.    TNode<Number> ToNumber(
12.        SloppyTNode<Context> context, SloppyTNode<Object> input,
13.        BigIntHandling bigint_handling = BigIntHandling::kThrow);
14.    TNode<Number> ToNumber_Inline(SloppyTNode<Context> context,
15.                                  SloppyTNode<Object> input);
16.    TNode<BigInt> ToBigInt(SloppyTNode<Context> context,
17.                           SloppyTNode<Object> input);
18.    TNode<Number> ToUint32(SloppyTNode<Context> context,
19.                           SloppyTNode<Object> input);
20.    // ES6 7.1.17 ToIndex, but jumps to range_error if the result is not a Smi.
21.    TNode<Smi> ToSmiIndex(TNode<Context> context, TNode<Object> input,
22.                          Label* range_error);
23.    TNode<Smi> ToSmiLength(TNode<Context> context, TNode<Object> input,
24.                           Label* range_error);
25.    TNode<Number> ToLength_Inline(SloppyTNode<Context> context,
26.                                  SloppyTNode<Object> input);
27.    TNode<Object> GetProperty(SloppyTNode<Context> context,
28.                              SloppyTNode<Object> receiver, Handle<Name> name) {}
29.    TNode<Object> GetProperty(SloppyTNode<Context> context,
30.                              SloppyTNode<Object> receiver,
31.                              SloppyTNode<Object> name) {}
32.    TNode<Object> SetPropertyStrict(TNode<Context> context,
33.                                    TNode<Object> receiver, TNode<Object> key,
34.                                    TNode<Object> value) {}
35.    template <class... TArgs>
36.    TNode<Object> CallBuiltin(Builtins::Name id, SloppyTNode<Object> context,
37.                              TArgs... args) {}
38.    template <class... TArgs>
39.    void TailCallBuiltin(Builtins::Name id, SloppyTNode<Object> context,
40.                         TArgs... args) {  }
41.    void LoadPropertyFromFastObject(...省略參數...);
42.    void LoadPropertyFromFastObject(...省略參數...);
43.    void LoadPropertyFromNameDictionary(...省略參數...);
44.    void LoadPropertyFromGlobalDictionary(...省略參數...);
45.    void UpdateFeedback(Node* feedback, Node* feedback_vector, Node* slot_id);
46.    void ReportFeedbackUpdate(TNode<FeedbackVector> feedback_vector,
47.                              SloppyTNode<UintPtrT> slot_id, const char* reason);
48.    void CombineFeedback(Variable* existing_feedback, int feedback);
49.    void CombineFeedback(Variable* existing_feedback, Node* feedback);
50.    void OverwriteFeedback(Variable* existing_feedback, int new_feedback);
51.    void BranchIfNumberRelationalComparison(Operation op,
52.                                            SloppyTNode<Number> left,
53.                                            SloppyTNode<Number> right,
54.                                            Label* if_true, Label* if_false);
55.    void BranchIfNumberEqual(TNode<Number> left, TNode<Number> right,
56.                             Label* if_true, Label* if_false) {
57.    }
58.  };

CodeStubAssembler 利用彙編語言實現了 JavaScript 的特有方法。基類 CodeAssembler 對彙編語言進行封裝, CodeStubAssembler 使用 CodeAssembler 提供的彙編功能實現了字符串轉換、屬性獲取和分支跳轉等 JavaScript 功能,這正是 CodeStubAssembler 的意義所在。
上述代碼第 4-9 行實現了字符串的相關操作;第 11-18 行代碼實現了類型轉換;第 21-26 行實現了 ES 規範中的功能;第 27-38 行實現了獲取和設置屬性;第 39-43 行實現了 Builtin 和 Runtime API 的調用方法;第 45-50 行代碼用於管理 Feedback;第 51-55 行實現了 IF 功能。
(4) CodeAssembler 封裝了彙編功能,實現了 Branch、Goto 等功能,源碼如下:

1.  class V8_EXPORT_PRIVATE CodeAssembler {
2.    void Branch(TNode<BoolT> condition,
3.                CodeAssemblerParameterizedLabel<T...>* if_true,
4.                CodeAssemblerParameterizedLabel<T...>* if_false, Args... args) {
5.      if_true->AddInputs(args...);
6.      if_false->AddInputs(args...);
7.      Branch(condition, if_true->plain_label(), if_false->plain_label());
8.    }
9.    template <class... T, class... Args>
10.    void Goto(CodeAssemblerParameterizedLabel<T...>* label, Args... args) {
11.      label->AddInputs(args...);
12.      Goto(label->plain_label());
13.    }
14.    void Branch(TNode<BoolT> condition, const std::function<void()>& true_body,
15.                const std::function<void()>& false_body);
16.    void Branch(TNode<BoolT> condition, Label* true_label,
17.                const std::function<void()>& false_body);
18.    void Branch(TNode<BoolT> condition, const std::function<void()>& true_body,
19.                Label* false_label);
20.    void Switch(Node* index, Label* default_label, const int32_t* case_values,
21.                Label** case_labels, size_t case_count);
22.  }

3 Compiler Pipeline

GenerateBytecodeHandler() 的第 22 行代碼完成了對 Bytecode LdaSmi 的編譯,源碼如下:

1.  Handle<Code> CodeAssembler::GenerateCode(CodeAssemblerState* state,
2.                                         const AssemblerOptions& options) {
3.  RawMachineAssembler* rasm = state->raw_assembler_.get();
4.  Handle<Code> code;
5.  Graph* graph = rasm->ExportForOptimization();
6.  code = Pipeline::GenerateCodeForCodeStub(...省略參數...)
7.              .ToHandleChecked();
8.   state->code_generated_ = true;
9.   return code;
10.  }
11.  //.............分隔線...................
12.  MaybeHandle<Code> Pipeline::GenerateCodeForCodeStub(...省略參數...) {
13.    OptimizedCompilationInfo info(CStrVector(debug_name), graph->zone(), kind);
14.    info.set_builtin_index(builtin_index);
15.    if (poisoning_level != PoisoningMitigationLevel::kDontPoison) {
16.      info.SetPoisoningMitigationLevel(poisoning_level);
17.    }
18.    // Construct a pipeline for scheduling and code generation.
19.    ZoneStats zone_stats(isolate->allocator());
20.    NodeOriginTable node_origins(graph);
21.    JumpOptimizationInfo jump_opt;
22.    bool should_optimize_jumps =
23.        isolate->serializer_enabled() && FLAG_turbo_rewrite_far_jumps;
24.    PipelineData data(&zone_stats, &info, isolate, isolate->allocator(), graph,
25.                      nullptr, source_positions, &node_origins,
26.                      should_optimize_jumps ? &jump_opt : nullptr, options);
27.    data.set_verify_graph(FLAG_verify_csa);
28.    std::unique_ptr<PipelineStatistics> pipeline_statistics;
29.    if (FLAG_turbo_stats || FLAG_turbo_stats_nvp) {
30.    }
31.    PipelineImpl pipeline(&data);
32.    if (info.trace_turbo_json_enabled() || info.trace_turbo_graph_enabled()) {//..省略...
33.    }
34.    pipeline.Run<CsaEarlyOptimizationPhase>();
35.    pipeline.RunPrintAndVerify(CsaEarlyOptimizationPhase::phase_name(), true);
36.    // .............省略..............
37.    PipelineData second_data(...省略參數...);
38.    second_data.set_verify_graph(FLAG_verify_csa);
39.    PipelineImpl second_pipeline(&second_data);
40.    second_pipeline.SelectInstructionsAndAssemble(call_descriptor);
41.    Handle<Code> code;
42.    if (jump_opt.is_optimizable()) {
43.      jump_opt.set_optimizing();
44.      code = pipeline.GenerateCode(call_descriptor).ToHandleChecked();
45.    } else {
46.      code = second_pipeline.FinalizeCode().ToHandleChecked();
47.    }
48.    return code;
49.  }

上述第 6 行代碼進入Pipeline開始編譯工作;第 13-29 用於設置 Pipeline 信息;第 32 行的使能標記在 flag-definitions.h 中定義,它們使用 Json 輸出當前的編譯信息;第 34-40 行代碼實現了生成初始彙編碼、對初始彙編碼進行優化、使用優化後的數據再次生成最終代碼等功能,注意 第 36 行代碼省略了優化初始彙編碼。圖1給出了 LdaSmi 的編譯結果。

技術總結
(1) 只有 v8_use_snapshot = false 時才能在 V8 中調試 Bytecode Handler 的編譯過程;
(2) CodeAssembler 封裝了彙編,CodeStubAssembler 封裝了JavaScript特有的功能,InterpreterAssembler 封裝了解釋器需要的功能,在這三層封裝之上是Bytecode Handler;
(3) V8 初始化時編譯包括 Byteocde handler 在內的所有 Builtin。
好了,今天到這裡,下次見。

個人能力有限,有不足與紕漏,歡迎批評指正
聯繫方式:請閱讀原文

本文由灰豆原創發布
轉載,請參考轉載聲明,註明出處: https://www.anquanke.com/post/id/262468
安全客 - 有思想的安全新媒體

關鍵字: