|
| 1 | +# 《Chrome V8 源码》43. Turbofan 源码分析 |
| 2 | + |
| 3 | +# 1 介绍 |
| 4 | +接上一篇文章继续说,本文讲解 Turbofan 的工作流程、梳理 PrepareJob、ExecuteJob 和 FinalizeJob 的主要功能以及重要数据结构。 |
| 5 | +# 2 Turbofan 工作流程 |
| 6 | +前文提到,Turbofan 分为 NotConcurrent 和 Concurrent 两种工作方式,它们的区别是 NotConcurrent 立即启动优化工作,而 Concurrent 把工作放进同步分发队列。 |
| 7 | +Concurrent 方式由 GetOptimizedCodeLater() 函数负责,其源码如下: |
| 8 | +```c++ |
| 9 | +1. bool GetOptimizedCodeLater(OptimizedCompilationJob* job, Isolate* isolate) { |
| 10 | +2. OptimizedCompilationInfo* compilation_info = job->compilation_info(); |
| 11 | +3. if (!isolate->optimizing_compile_dispatcher()->IsQueueAvailable()) { |
| 12 | +4. if (FLAG_trace_concurrent_recompilation) { |
| 13 | +5. //省略................ |
| 14 | +6. } |
| 15 | +7. return false; |
| 16 | +8. } |
| 17 | +9. if (isolate->heap()->HighMemoryPressure()) { |
| 18 | +10. if (FLAG_trace_concurrent_recompilation) { |
| 19 | +11. //省略................ |
| 20 | +12. } |
| 21 | +13. return false; |
| 22 | +14. } |
| 23 | +15. TimerEventScope<TimerEventRecompileSynchronous> timer(isolate); |
| 24 | +16. RuntimeCallTimerScope runtimeTimer( |
| 25 | +17. isolate, RuntimeCallCounterId::kOptimizeConcurrentPrepare); |
| 26 | +18. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| 27 | +19. "V8.OptimizeConcurrentPrepare"); |
| 28 | +20. if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED) return false; |
| 29 | +21. isolate->optimizing_compile_dispatcher()->QueueForOptimization(job); |
| 30 | +22. if (FLAG_trace_concurrent_recompilation) { |
| 31 | +23. PrintF(" ** Queued "); |
| 32 | +24. compilation_info->closure()->ShortPrint(); |
| 33 | +25. PrintF(" for concurrent optimization.\n"); |
| 34 | +26. } |
| 35 | +27. return true; |
| 36 | +28. } |
| 37 | +``` |
| 38 | +上述代码中,第 4-14 行检测工作队列和内存是否满足要求,不满足则停止优化编译。停止优化编译不影响当前 JavaScript 程序的运行,因为 JavaScript 程序正在被解释执行。 |
| 39 | +第 15-20 行统计 V8 运行信息,与优化编译的功能无关; |
| 40 | +第 21 行把优化编译工作 job 添加到工作队列中,并返回结果 true。 |
| 41 | +NotConcurrent 方式由 GetOptimizedCodeNow() 函数负责,其源码如下: |
| 42 | +```c++ |
| 43 | +1. bool GetOptimizedCodeNow(OptimizedCompilationJob* job, Isolate* isolate) { |
| 44 | +2. TimerEventScope<TimerEventRecompileSynchronous> timer(isolate); |
| 45 | +3. RuntimeCallTimerScope runtimeTimer( |
| 46 | +4. isolate, RuntimeCallCounterId::kOptimizeNonConcurrent); |
| 47 | +5. OptimizedCompilationInfo* compilation_info = job->compilation_info(); |
| 48 | +6. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), |
| 49 | +7. "V8.OptimizeNonConcurrent"); |
| 50 | +8. if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED || |
| 51 | +9. job->ExecuteJob(isolate->counters()->runtime_call_stats()) != |
| 52 | +10. CompilationJob::SUCCEEDED || |
| 53 | +11. job->FinalizeJob(isolate) != CompilationJob::SUCCEEDED) { |
| 54 | +12. if (FLAG_trace_opt) { |
| 55 | +13. CodeTracer::Scope scope(isolate->GetCodeTracer()); |
| 56 | +14. PrintF(scope.file(), "[aborted optimizing "); |
| 57 | +15. compilation_info->closure()->ShortPrint(scope.file()); |
| 58 | +16. PrintF(scope.file(), " because: %s]\n", |
| 59 | +17. GetBailoutReason(compilation_info->bailout_reason())); |
| 60 | +18. } |
| 61 | +19. return false; |
| 62 | +20. } |
| 63 | +21. // Success! |
| 64 | +22. job->RecordCompilationStats(OptimizedCompilationJob::kSynchronous, isolate); |
| 65 | +23. DCHECK(!isolate->has_pending_exception()); |
| 66 | +24. InsertCodeIntoOptimizedCodeCache(compilation_info); |
| 67 | +25. job->RecordFunctionCompilation(CodeEventListener::LAZY_COMPILE_TAG, isolate); |
| 68 | +26. return true; |
| 69 | +27. } |
| 70 | +``` |
| 71 | +上述代码中, 第 2-7 行统计 V8 运行信息,与优化编译的功能无关; |
| 72 | +第 8-9 行完成优化编译的所有工作,这些工作由 PrepareJob、ExecuteJob 以及 FinalizeJob 三个函数负责; |
| 73 | +第 10-25 行更新编译状态等信息并返回 true。 优化编译同步进行,也就意味着暂停解释执行并等待优化编译的结果。 |
| 74 | +# 3 准备 PrepareJob |
| 75 | +源码如下: |
| 76 | +```c++ |
| 77 | +1. CompilationJob::Status OptimizedCompilationJob::PrepareJob(Isolate* isolate) { |
| 78 | +2. DCHECK_EQ(ThreadId::Current(), isolate->thread_id()); |
| 79 | +3. DisallowJavascriptExecution no_js(isolate); |
| 80 | +4. if (FLAG_trace_opt && compilation_info()->IsOptimizing()) { |
| 81 | +5. //省略.............. |
| 82 | +6. } |
| 83 | +7. // Delegate to the underlying implementation. |
| 84 | +8. DCHECK_EQ(state(), State::kReadyToPrepare); |
| 85 | +9. ScopedTimer t(&time_taken_to_prepare_); |
| 86 | +10. return UpdateState(PrepareJobImpl(isolate), State::kReadyToExecute); |
| 87 | +11. } |
| 88 | +``` |
| 89 | +上述代码中,第 2-3 行做状态检测、第 4-6 行设置打印出输信息;第 10 行 UpdateState 更新状态信息,PrepareJobImpl 完成初始化工作,其源码如下: |
| 90 | +```c++ |
| 91 | +1. PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl( |
| 92 | +2. Isolate* isolate) { |
| 93 | +3. PipelineJobScope scope(&data_, isolate->counters()->runtime_call_stats()); |
| 94 | +4. if (compilation_info()->bytecode_array()->length() > |
| 95 | +5. FLAG_max_optimized_bytecode_size) { |
| 96 | +6. return AbortOptimization(BailoutReason::kFunctionTooBig); |
| 97 | +7. } |
| 98 | +8. if (!FLAG_always_opt) { |
| 99 | +9. compilation_info()->MarkAsBailoutOnUninitialized(); |
| 100 | +10. } |
| 101 | +11. if (FLAG_turbo_loop_peeling) { |
| 102 | +12. compilation_info()->MarkAsLoopPeelingEnabled(); |
| 103 | +13. } |
| 104 | +14. if (FLAG_turbo_inlining) { |
| 105 | +15. compilation_info()->MarkAsInliningEnabled(); |
| 106 | +16. } |
| 107 | +17. PoisoningMitigationLevel load_poisoning = |
| 108 | +18. PoisoningMitigationLevel::kDontPoison; |
| 109 | +19. if (FLAG_untrusted_code_mitigations) { |
| 110 | +20. load_poisoning = PoisoningMitigationLevel::kPoisonCriticalOnly; |
| 111 | +21. } |
| 112 | +22. compilation_info()->SetPoisoningMitigationLevel(load_poisoning); |
| 113 | +23. if (FLAG_turbo_allocation_folding) { |
| 114 | +24. compilation_info()->MarkAsAllocationFoldingEnabled(); |
| 115 | +25. } |
| 116 | +26. if (compilation_info()->closure()->raw_feedback_cell().map() == |
| 117 | +27. ReadOnlyRoots(isolate).one_closure_cell_map() && |
| 118 | +28. !compilation_info()->is_osr()) { |
| 119 | +29. compilation_info()->MarkAsFunctionContextSpecializing(); |
| 120 | +30. data_.ChooseSpecializationContext(); |
| 121 | +31. } |
| 122 | +32. if (compilation_info()->is_source_positions_enabled()) { |
| 123 | +33. SharedFunctionInfo::EnsureSourcePositionsAvailable( |
| 124 | +34. isolate, compilation_info()->shared_info()); |
| 125 | +35. } |
| 126 | +36. data_.set_start_source_position( |
| 127 | +37. compilation_info()->shared_info()->StartPosition()); |
| 128 | +38. linkage_ = new (compilation_info()->zone()) Linkage( |
| 129 | +39. Linkage::ComputeIncoming(compilation_info()->zone(), compilation_info())); |
| 130 | +40. if (compilation_info()->is_osr()) data_.InitializeOsrHelper(); |
| 131 | +41. Deoptimizer::EnsureCodeForDeoptimizationEntries(isolate); |
| 132 | +42. pipeline_.Serialize(); |
| 133 | +43. if (!data_.broker()->is_concurrent_inlining()) { |
| 134 | +44. if (!pipeline_.CreateGraph()) { |
| 135 | +45. CHECK(!isolate->has_pending_exception()); |
| 136 | +46. return AbortOptimization(BailoutReason::kGraphBuildingFailed); |
| 137 | +47. } |
| 138 | +48. } |
| 139 | +49. return SUCCEEDED; |
| 140 | +50. } |
| 141 | +``` |
| 142 | +上述代码中,第 4-7 行检查 BytecodeArray 的长度是否超过最大长度限制; |
| 143 | +第 8-10 行检查 always_optimization 使能标记,它的作用是 always try to optimize functions; |
| 144 | +第 11-25 行检测 loop_peeling、inling、allocation_folding 使能标记,详细说明参见 flag-definitions.h 文件; |
| 145 | +第 26-37 行设置 context、OSR、源码信息; |
| 146 | +第 38 行创建编译需要的 link 信息; |
| 147 | +第 44 行创建 V8.TFGraph,这之后不再需要 `T<Node>`了; |
| 148 | +# 4 编译 ExecuteJob |
| 149 | +ExecuteJob() 中调用 ExecuteJobImpl() 来完成优化编译的主体工作,其源码如下: |
| 150 | +```c++ |
| 151 | +1. PipelineCompilationJob::Status PipelineCompilationJob::ExecuteJobImpl( |
| 152 | +2. RuntimeCallStats* stats) { |
| 153 | +3. PipelineJobScope scope(&data_, stats); |
| 154 | +4. if (data_.broker()->is_concurrent_inlining()) { |
| 155 | +5. //省略..... |
| 156 | +6. } |
| 157 | +7. bool success; |
| 158 | +8. if (FLAG_turboprop) { |
| 159 | +9. success = pipeline_.OptimizeGraphForMidTier(linkage_); |
| 160 | +10. } else { |
| 161 | +11. success = pipeline_.OptimizeGraph(linkage_); |
| 162 | +12. } |
| 163 | +13. if (!success) return FAILED; |
| 164 | +14. pipeline_.AssembleCode(linkage_); |
| 165 | +15. return SUCCEEDED; |
| 166 | +``` |
| 167 | +上述代码的核心功能就两个,一个基于图的优化功能(OptimizeGraphForMidTier 和 OptimizeGraph),另一个汇编生成器(AssembleCode)。优化功能的源码如下: |
| 168 | +```c++ |
| 169 | +bool PipelineImpl::OptimizeGraphForMidTier(Linkage* linkage) { |
| 170 | + Run<TyperPhase>(data->CreateTyper()); |
| 171 | + RunPrintAndVerify(TyperPhase::phase_name()); |
| 172 | + Run<TypedLoweringPhase>(); |
| 173 | + RunPrintAndVerify(TypedLoweringPhase::phase_name()); |
| 174 | + Run<LoopExitEliminationPhase>(); |
| 175 | + //省略.............. |
| 176 | +} |
| 177 | +//分隔线.................... |
| 178 | +bool PipelineImpl::OptimizeGraph(Linkage* linkage) { |
| 179 | + PipelineData* data = this->data_; |
| 180 | + data->BeginPhaseKind("V8.TFLowering"); |
| 181 | + Run<TyperPhase>(data->CreateTyper()); |
| 182 | + RunPrintAndVerify(TyperPhase::phase_name()); |
| 183 | + Run<TypedLoweringPhase>(); |
| 184 | + RunPrintAndVerify(TypedLoweringPhase::phase_name()); |
| 185 | + //省略.............. |
| 186 | +} |
| 187 | +``` |
| 188 | +上述代码中,每一个 Run 方法代表过了一种优化技术,每种优化技术的实现都有对应的数据结构,本文不做讲解。 |
| 189 | +汇编生成器(AssembleCode)的源码如下: |
| 190 | +```c++ |
| 191 | +1. void PipelineImpl::AssembleCode(Linkage* linkage, |
| 192 | +2. std::unique_ptr<AssemblerBuffer> buffer) { |
| 193 | +3. PipelineData* data = this->data_; |
| 194 | +4. data->BeginPhaseKind("V8.TFCodeGeneration"); |
| 195 | +5. data->InitializeCodeGenerator(linkage, std::move(buffer)); |
| 196 | +6. Run<AssembleCodePhase>(); |
| 197 | +7. //省略..... |
| 198 | +8. } |
| 199 | +9. //分隔................. |
| 200 | +10. CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( |
| 201 | +11. Instruction* instr) { |
| 202 | +12. switch (arch_opcode) { |
| 203 | +13. case kArchCallCodeObject: { |
| 204 | +14. if (HasImmediateInput(instr, 0)) { |
| 205 | +15. //省略....................... |
| 206 | +16. } else { |
| 207 | +17. Register reg = i.InputRegister(0); |
| 208 | +18. DCHECK_IMPLIES( |
| 209 | +19. HasCallDescriptorFlag(instr, CallDescriptor::kFixedTargetRegister), |
| 210 | +20. reg == kJavaScriptCallCodeStartRegister); |
| 211 | +21. __ LoadCodeObjectEntry(reg, reg); |
| 212 | +22. if (HasCallDescriptorFlag(instr, CallDescriptor::kRetpoline)) { |
| 213 | +23. __ RetpolineCall(reg); |
| 214 | +24. } else { |
| 215 | +25. __ call(reg); |
| 216 | +26. } } |
| 217 | +27. RecordCallPosition(instr); |
| 218 | +28. frame_access_state()->ClearSPDelta(); |
| 219 | +29. break; |
| 220 | +30. } |
| 221 | +31. case kArchCallBuiltinPointer: { |
| 222 | +32. //省略....................... |
| 223 | +33. break; |
| 224 | +34. }} |
| 225 | +35. } |
| 226 | +``` |
| 227 | +上述第 5 行代码初始 CodeGenerator,第 6 行代码 Run() 方法最终会调用第 10 行 AssembleArchInstruction() 方法以完成汇编码的生成。第 12-34 行代码采用 switch-case 为每条操作码(OPCODE)编写不同的汇编码生成规则。每条操作码对应一个 case,这个 case 描绘了把操作码转换为汇编码的规则。图 1 给出了 AssembleArchInstruction 的调用堆栈。 |
| 228 | + |
| 229 | +V8 中 OPCODE 分为两类,一类是体系结构通用的操作码(COMMON_ARCH_OPCODE_LIST),另一类是体系结构专用的操作码(TARGET_ARCH_OPCODE_LIST),具体参见宏模板。 |
| 230 | +# 5 收尾 FinalizeJob |
| 231 | +收尾工作由 FinalizeJobImpl() 负责,源码如下: |
| 232 | +```c++ |
| 233 | +1. PipelineCompilationJob::Status PipelineCompilationJob::FinalizeJobImpl( |
| 234 | +2. Isolate* isolate) { |
| 235 | +3. //省略................. |
| 236 | +4. MaybeHandle<Code> maybe_code = pipeline_.FinalizeCode(); |
| 237 | +5. Handle<Code> code; |
| 238 | +6. if (!maybe_code.ToHandle(&code)) { |
| 239 | +7. //省略................. |
| 240 | +8. } |
| 241 | +9. if (!pipeline_.CommitDependencies(code)) { |
| 242 | +10. //省略................. |
| 243 | +11. } |
| 244 | +12. compilation_info()->SetCode(code); |
| 245 | +13. compilation_info()->native_context().AddOptimizedCode(*code); |
| 246 | +14. RegisterWeakObjectsInOptimizedCode(code, isolate); |
| 247 | +15. return SUCCEEDED; |
| 248 | +16. } |
| 249 | +``` |
| 250 | +上述第 4 行代码接收优化编译的结果;第 6-8 行代码优化编译失败并返回 false;第 9-11 行代码重试优化编译;第 12 行代码将优化编译结果存储进 Cache,下次再优化该 SharedFunction 时将直接使用 Cache 结果。 |
| 251 | +**技术总结** |
| 252 | +**(1)** --Trace-XXX 用于打印编译状态和结果,参见 d8 --help 或 flag-definitions.h; |
| 253 | +**(2)** 优化编译的使能标记的定义在 flag-definitions.h 中; |
| 254 | +**(3)** On-Stack Replacement(OSR)是一种运行时替换函数的栈帧的方法。 |
| 255 | +# 新文章介绍 |
| 256 | +**《Chrome V8 Bug》** 系列文章即将上线。 |
| 257 | +《Chrome V8 Bug》系列文章的目的是解释漏洞的产生原因,并向你展示这些漏洞如何影响 V8 的正确性。其他的漏洞文章大多从安全研究的角度分析,讲述如何设计与使用 PoC。而本系列文章是从源码研究的角度来写的,分析 PoC 在 V8 中的执行细节,讲解为什么 Poc 要这样设计。当然,学习 Poc 的设计与使用,是 V8 安全研究的很好的出发点,所以,对于希望深入学习 V8 源码和 PoC 原理的人来说,本系列文章也是很有价值的介绍性读物。 |
| 258 | +本系列文章主要讲解 https://bugs.chromium.org/p/v8/issues 的内容,每篇文章讲解一个 issue。如果你有想学习的 issue 也可以告诉我,我会优先分析讲解。 |
| 259 | + |
| 260 | +好了,今天到这里,下次见。 |
| 261 | +**个人能力有限,有不足与纰漏,欢迎批评指正** |
| 262 | +**微信:qq9123013 备注:v8交流 邮箱:v8blink@outlook.com** |
| 263 | + |
0 commit comments