1 介绍
接上一篇文章继续说,本文讲解 Turbofan 的工作流程、梳理 PrepareJob、ExecuteJob 和 FinalizeJob 的主要功能以及重要数据结构。
2 Turbofan 工作流程
前文提到,Turbofan 分为 NotConcurrent 和 Concurrent 两种工作方式,它们的区别是 NotConcurrent 立即启动优化工作,而 Concurrent 把工作放进同步分发队列。
Concurrent 方式由 GetOptimizedCodeLater() 函数负责,其源码如下:
1. bool GetOptimizedCodeLater(OptimizedCompilationJob* job, Isolate* isolate) {
2. OptimizedCompilationInfo* compilation_info = job->compilation_info();
3. if (!isolate->optimizing_compile_dispatcher()->IsQueueAvailable()) {
4. if (FLAG_trace_concurrent_recompilation) {
5. //省略................
6. }
7. return false;
8. }
9. if (isolate->heap()->HighMemoryPressure()) {
10. if (FLAG_trace_concurrent_recompilation) {
11. //省略................
12. }
13. return false;
14. }
15. TimerEventScope<TimerEventRecompileSynchronous> timer(isolate);
16. RuntimeCallTimerScope runtimeTimer(
17. isolate, RuntimeCallCounterId::kOptimizeConcurrentPrepare);
18. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
19. "V8.OptimizeConcurrentPrepare");
20. if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED) return false;
21. isolate->optimizing_compile_dispatcher()->QueueForOptimization(job);
22. if (FLAG_trace_concurrent_recompilation) {
23. PrintF(" ** Queued ");
24. compilation_info->closure()->ShortPrint();
25. PrintF(" for concurrent optimization.\n");
26. }
27. return true;
28. }
上述代码中,第 4-14 行检测工作队列和内存是否满足要求,不满足则停止优化编译。停止优化编译不影响当前 JavaScript 程序的运行,因为 JavaScript 程序正在被解释执行。
第 15-20 行统计 V8 运行信息,与优化编译的功能无关;
第 21 行把优化编译工作 job 添加到工作队列中,并返回结果 true。
NotConcurrent 方式由 GetOptimizedCodeNow() 函数负责,其源码如下:
1. bool GetOptimizedCodeNow(OptimizedCompilationJob* job, Isolate* isolate) {
2. TimerEventScope<TimerEventRecompileSynchronous> timer(isolate);
3. RuntimeCallTimerScope runtimeTimer(
4. isolate, RuntimeCallCounterId::kOptimizeNonConcurrent);
5. OptimizedCompilationInfo* compilation_info = job->compilation_info();
6. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
7. "V8.OptimizeNonConcurrent");
8. if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED ||
9. job->ExecuteJob(isolate->counters()->runtime_call_stats()) !=
10. CompilationJob::SUCCEEDED ||
11. job->FinalizeJob(isolate) != CompilationJob::SUCCEEDED) {
12. if (FLAG_trace_opt) {
13. CodeTracer::Scope scope(isolate->GetCodeTracer());
14. PrintF(scope.file(), "[aborted optimizing ");
15. compilation_info->closure()->ShortPrint(scope.file());
16. PrintF(scope.file(), " because: %s]\n",
17. GetBailoutReason(compilation_info->bailout_reason()));
18. }
19. return false;
20. }
21. // Success!
22. job->RecordCompilationStats(OptimizedCompilationJob::kSynchronous, isolate);
23. DCHECK(!isolate->has_pending_exception());
24. InsertCodeIntoOptimizedCodeCache(compilation_info);
25. job->RecordFunctionCompilation(CodeEventListener::LAZY_COMPILE_TAG, isolate);
26. return true;
27. }
上述代码中, 第 2-7 行统计 V8 运行信息,与优化编译的功能无关;
第 8-9 行完成优化编译的所有工作,这些工作由 PrepareJob、ExecuteJob 以及 FinalizeJob 三个函数负责;
第 10-25 行更新编译状态等信息并返回 true。 优化编译同步进行,也就意味着暂停解释执行并等待优化编译的结果。
3 准备 PrepareJob
源码如下:
1. CompilationJob::Status OptimizedCompilationJob::PrepareJob(Isolate* isolate) {
2. DCHECK_EQ(ThreadId::Current(), isolate->thread_id());
3. DisallowJavascriptExecution no_js(isolate);
4. if (FLAG_trace_opt && compilation_info()->IsOptimizing()) {
5. //省略..............
6. }
7. // Delegate to the underlying implementation.
8. DCHECK_EQ(state(), State::kReadyToPrepare);
9. ScopedTimer t(&time_taken_to_prepare_);
10. return UpdateState(PrepareJobImpl(isolate), State::kReadyToExecute);
11. }
上述代码中,第 2-3 行做状态检测、第 4-6 行设置打印出输信息;第 10 行 UpdateState 更新状态信息,PrepareJobImpl 完成初始化工作,其源码如下:
1. PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl(
2. Isolate* isolate) {
3. PipelineJobScope scope(&data_, isolate->counters()->runtime_call_stats());
4. if (compilation_info()->bytecode_array()->length() >
5. FLAG_max_optimized_bytecode_size) {
6. return AbortOptimization(BailoutReason::kFunctionTooBig);
7. }
8. if (!FLAG_always_opt) {
9. compilation_info()->MarkAsBailoutOnUninitialized();
10. }
11. if (FLAG_turbo_loop_peeling) {
12. compilation_info()->MarkAsLoopPeelingEnabled();
13. }
14. if (FLAG_turbo_inlining) {
15. compilation_info()->MarkAsInliningEnabled();
16. }
17. PoisoningMitigationLevel load_poisoning =
18. PoisoningMitigationLevel::kDontPoison;
19. if (FLAG_untrusted_code_mitigations) {
20. load_poisoning = PoisoningMitigationLevel::kPoisonCriticalOnly;
21. }
22. compilation_info()->SetPoisoningMitigationLevel(load_poisoning);
23. if (FLAG_turbo_allocation_folding) {
24. compilation_info()->MarkAsAllocationFoldingEnabled();
25. }
26. if (compilation_info()->closure()->raw_feedback_cell().map() ==
27. ReadOnlyRoots(isolate).one_closure_cell_map() &&
28. !compilation_info()->is_osr()) {
29. compilation_info()->MarkAsFunctionContextSpecializing();
30. data_.ChooseSpecializationContext();
31. }
32. if (compilation_info()->is_source_positions_enabled()) {
33. SharedFunctionInfo::EnsureSourcePositionsAvailable(
34. isolate, compilation_info()->shared_info());
35. }
36. data_.set_start_source_position(
37. compilation_info()->shared_info()->StartPosition());
38. linkage_ = new (compilation_info()->zone()) Linkage(
39. Linkage::ComputeIncoming(compilation_info()->zone(), compilation_info()));
40. if (compilation_info()->is_osr()) data_.InitializeOsrHelper();
41. Deoptimizer::EnsureCodeForDeoptimizationEntries(isolate);
42. pipeline_.Serialize();
43. if (!data_.broker()->is_concurrent_inlining()) {
44. if (!pipeline_.CreateGraph()) {
45. CHECK(!isolate->has_pending_exception());
46. return AbortOptimization(BailoutReason::kGraphBuildingFailed);
47. }
48. }
49. return SUCCEEDED;
50. }
上述代码中,第 4-7 行检查 BytecodeArray 的长度是否超过最大长度限制;
第 8-10 行检查 always_optimization 使能标记,它的作用是 always try to optimize functions;
第 11-25 行检测 loop_peeling、inling、allocation_folding 使能标记,详细说明参见 flag-definitions.h 文件;
第 26-37 行设置 context、OSR、源码信息;
第 38 行创建编译需要的 link 信息;
第 44 行创建 V8.TFGraph,这之后不再需要 T<Node>
了;
4 编译 ExecuteJob
ExecuteJob() 中调用 ExecuteJobImpl() 来完成优化编译的主体工作,其源码如下:
1. PipelineCompilationJob::Status PipelineCompilationJob::ExecuteJobImpl(
2. RuntimeCallStats* stats) {
3. PipelineJobScope scope(&data_, stats);
4. if (data_.broker()->is_concurrent_inlining()) {
5. //省略.....
6. }
7. bool success;
8. if (FLAG_turboprop) {
9. success = pipeline_.OptimizeGraphForMidTier(linkage_);
10. } else {
11. success = pipeline_.OptimizeGraph(linkage_);
12. }
13. if (!success) return FAILED;
14. pipeline_.AssembleCode(linkage_);
15. return SUCCEEDED;
上述代码的核心功能就两个,一个基于图的优化功能(OptimizeGraphForMidTier 和 OptimizeGraph),另一个汇编生成器(AssembleCode)。优化功能的源码如下:
bool PipelineImpl::OptimizeGraphForMidTier(Linkage* linkage) {
Run<TyperPhase>(data->CreateTyper());
RunPrintAndVerify(TyperPhase::phase_name());
Run<TypedLoweringPhase>();
RunPrintAndVerify(TypedLoweringPhase::phase_name());
Run<LoopExitEliminationPhase>();
//省略..............
}
//分隔线....................
bool PipelineImpl::OptimizeGraph(Linkage* linkage) {
PipelineData* data = this->data_;
data->BeginPhaseKind("V8.TFLowering");
Run<TyperPhase>(data->CreateTyper());
RunPrintAndVerify(TyperPhase::phase_name());
Run<TypedLoweringPhase>();
RunPrintAndVerify(TypedLoweringPhase::phase_name());
//省略..............
}
上述代码中,每一个 Run 方法代表过了一种优化技术,每种优化技术的实现都有对应的数据结构,本文不做讲解。
汇编生成器(AssembleCode)的源码如下:
1. void PipelineImpl::AssembleCode(Linkage* linkage,
2. std::unique_ptr<AssemblerBuffer> buffer) {
3. PipelineData* data = this->data_;
4. data->BeginPhaseKind("V8.TFCodeGeneration");
5. data->InitializeCodeGenerator(linkage, std::move(buffer));
6. Run<AssembleCodePhase>();
7. //省略.....
8. }
9. //分隔.................
10. CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
11. Instruction* instr) {
12. switch (arch_opcode) {
13. case kArchCallCodeObject: {
14. if (HasImmediateInput(instr, 0)) {
15. //省略.......................
16. } else {
17. Register reg = i.InputRegister(0);
18. DCHECK_IMPLIES(
19. HasCallDescriptorFlag(instr, CallDescriptor::kFixedTargetRegister),
20. reg == kJavaScriptCallCodeStartRegister);
21. __ LoadCodeObjectEntry(reg, reg);
22. if (HasCallDescriptorFlag(instr, CallDescriptor::kRetpoline)) {
23. __ RetpolineCall(reg);
24. } else {
25. __ call(reg);
26. } }
27. RecordCallPosition(instr);
28. frame_access_state()->ClearSPDelta();
29. break;
30. }
31. case kArchCallBuiltinPointer: {
32. //省略.......................
33. break;
34. }}
35. }
上述第 5 行代码初始 CodeGenerator,第 6 行代码 Run() 方法最终会调用第 10 行 AssembleArchInstruction() 方法以完成汇编码的生成。第 12-34 行代码采用 switch-case 为每条操作码(OPCODE)编写不同的汇编码生成规则。每条操作码对应一个 case,这个 case 描绘了把操作码转换为汇编码的规则。图 1 给出了 AssembleArchInstruction 的调用堆栈。
V8 中 OPCODE 分为两类,一类是体系结构通用的操作码(COMMON_ARCH_OPCODE_LIST),另一类是体系结构专用的操作码(TARGET_ARCH_OPCODE_LIST),具体参见宏模板。
5 收尾 FinalizeJob
收尾工作由 FinalizeJobImpl() 负责,源码如下:
1. PipelineCompilationJob::Status PipelineCompilationJob::FinalizeJobImpl(
2. Isolate* isolate) {
3. //省略.................
4. MaybeHandle<Code> maybe_code = pipeline_.FinalizeCode();
5. Handle<Code> code;
6. if (!maybe_code.ToHandle(&code)) {
7. //省略.................
8. }
9. if (!pipeline_.CommitDependencies(code)) {
10. //省略.................
11. }
12. compilation_info()->SetCode(code);
13. compilation_info()->native_context().AddOptimizedCode(*code);
14. RegisterWeakObjectsInOptimizedCode(code, isolate);
15. return SUCCEEDED;
16. }
上述第 4 行代码接收优化编译的结果;第 6-8 行代码优化编译失败并返回 false;第 9-11 行代码重试优化编译;第 12 行代码将优化编译结果存储进 Cache,下次再优化该 SharedFunction 时将直接使用 Cache 结果。
技术总结
(1) —Trace-XXX 用于打印编译状态和结果,参见 d8 —help 或 flag-definitions.h;
(2) 优化编译的使能标记的定义在 flag-definitions.h 中;
(3) On-Stack Replacement(OSR)是一种运行时替换函数的栈帧的方法。
新文章介绍
《Chrome V8 Bug》 系列文章即将上线。
《Chrome V8 Bug》系列文章的目的是解释漏洞的产生原因,并向你展示这些漏洞如何影响 V8 的正确性。其他的漏洞文章大多从安全研究的角度分析,讲述如何设计与使用 PoC。而本系列文章是从源码研究的角度来写的,分析 PoC 在 V8 中的执行细节,讲解为什么 Poc 要这样设计。当然,学习 Poc 的设计与使用,是 V8 安全研究的很好的出发点,所以,对于希望深入学习 V8 源码和 PoC 原理的人来说,本系列文章也是很有价值的介绍性读物。
本系列文章主要讲解 https://bugs.chromium.org/p/v8/issues 的内容,每篇文章讲解一个 issue。如果你有想学习的 issue 也可以告诉我,我会优先讲解。
好了,今天到这里,下次见。
个人能力有限,有不足与纰漏,欢迎批评指正
微信:qq9123013 备注:v8交流 邮箱:v8blink@outlook.com