介绍

接上一篇文章继续说,本文讲解 Turbofan 的工作流程、梳理 PrepareJob、ExecuteJob 和 FinalizeJob 的主要功能以及重要数据结构。

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 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.");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 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]",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。优化编译同步进行,也就意味着暂停解释执行并等待优化编译的结果。

准备 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了;

编译 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(data->CreateTyper()); RunPrintAndVerify(TyperPhase::phase_name()); Run(); RunPrintAndVerify(TypedLoweringPhase::phase_name()); Run(); //省略..............}//分隔线....................bool PipelineImpl::OptimizeGraph(Linkage* linkage) {  PipelineData* data = this->data_;  data->BeginPhaseKind("V8.TFLowering");  Run(data->CreateTyper());  RunPrintAndVerify(TyperPhase::phase_name());  Run();  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),具体参见宏模板。

收尾 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 也可以告诉我,我会优先讲解。