Skip to content

TurboFan简单理解 #98

Description

@xinali

TurboFan简单理解

这两天在整理以前写的很多本地技术文章,但是从来没有在线发布过,所以打算在这里做一下备份,这篇是2023年11月编写

环境

具体测试环境

ubuntu: 22.04.03 x64
chromium version: 70.0.3538.9
v8 version: 7.0.276.3
depot_tools: eabc276ceacd0df346c53736b38fd3a7d0f7ac2f

遇到的问题

问题一:
编译完chromium之后,启动chrome失败,提示初始化数据库失败

xinali@ubuntu:~/chromium/src/out/Release$ ./chrome
[85552:85552:1017/094100.902077:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094100.902278:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094100.902369:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094100.902471:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094100.903750:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85593:1017/094100.911720:WARNING:web_database.cc(115)] Web database is too new.
[85552:85593:1017/094100.912728:ERROR:web_database_backend.cc(113)] Cannot initialize the web database: 2 <--- 初始化数据库失败
[85552:85552:1017/094100.928617:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094100.975854:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094101.011474:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094101.011900:WARNING:account_consistency_mode_manager.cc(283)] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client secret have been configured.
[85552:85552:1017/094101.025249:FATAL:password_store_x.cc(85)] Check failed: false. 
#0 0x7faa6d351bed base::debug::StackTrace::StackTrace()
#1 0x7faa6d0589ac base::debug::StackTrace::StackTrace()
#2 0x7faa6d0c82ca logging::LogMessage::~LogMessage()
#3 0x56540e7cbadb (anonymous namespace)::StepForMetrics()
#4 0x56540e7cb849 PasswordStoreX::PasswordStoreX()
#5 0x56540e1661e2 PasswordStoreFactory::BuildServiceInstanceFor()
#6 0x7faa5aab8200 RefcountedBrowserContextKeyedServiceFactory::BuildServiceInstanceFor()
#7 0x7faa5bfffed2 RefcountedKeyedServiceFactory::GetServiceForContext()
#8 0x7faa5aab7fde RefcountedBrowserContextKeyedServiceFactory::GetServiceForBrowserContext()
#9 0x56540e164c40 PasswordStoreFactory::GetForProfile()
#10 0x56540e498860 browser_sync::ChromeSyncClient::Initialize()
#11 0x56541153f3b3 browser_sync::ProfileSyncService::Initialize()
#12 0x56540e4a3b66 ProfileSyncServiceFactory::BuildServiceInstanceFor()
#13 0x7faa5aab59ec BrowserContextKeyedServiceFactory::BuildServiceInstanceFor()
#14 0x7faa5bff8a98 KeyedServiceFactory::GetServiceForContext()
#15 0x7faa5aab57a7 BrowserContextKeyedServiceFactory::GetServiceForBrowserContext()
#16 0x56540e4a286c ProfileSyncServiceFactory::GetSyncServiceForBrowserContext()
#17 0x56540e4a2825 ProfileSyncServiceFactory::GetForProfile()
#18 0x56540e986188 SupervisedUserService::Init()
#19 0x56540e39e666 ProfileManager::DoFinalInitForServices()
#20 0x56540e39e3c4 ProfileManager::DoFinalInit()
#21 0x56540e3a00ba ProfileManager::AddProfile()

解决方案: 删除~/.config/chromium相关配置信息
再次启动,成功初始化,并运行

问题二:

使用ubuntu 18.04系统直接安装的nodejs和npm编译v8的tools/turbolizer失败
解决方案:使用v8官方github.io的工具,工具地址: https://v8.github.io/tools/head/turbolizer/index.html
CTL+L上传对应的生成的json文件

跟踪调试Turbofan

通过样例分析v8编译优化过程

  1. 生成turbolizer数据
// 目标优化函数  
function opt_me(b) {  
    let values = [42,1337];  
    let x = 10;  
    if (b == "foo")  
      x = 5;  
                           
    let y = x + 2;  
    y = y + 1000;  
    y = y * 2;  
    y = y & 10;  
    y = y / 3;  
    y = y & 1;  
    return values[y];  
}  
// 必须!在优化该函数前必须先进行一次编译,以便于为该函数提供type feedback  
opt_me();  
// 必须! 使用v8 natives-syntax来强制优化该函数  
%OptimizeFunctionOnNextCall(opt_me);  
// 必须! 不调用目标函数则无法执行优化  
opt_me();

执行:out/Release/d8 test.js --allow-natives-syntax --trace-turbo
将生成的json文件导入turbolizer查看具体结构

  1. 代码优化
out/Release/d8 test.js --allow-natives-syntax --trace-opt
[manually marking 0x1e82326a3779 <JSFunction opt_me (sfi = 0x1e82326a3591)> for non-concurrent optimization]
[compiling method 0x1e82326a3779 <JSFunction opt_me (sfi = 0x1e82326a3591)> using TurboFan]
[optimizing 0x1e82326a3779 <JSFunction opt_me (sfi = 0x1e82326a3591)> - took 8.588, 3.554, 0.157 ms]
  1. 跟踪优化与反优化
class Player{}  
class Wall{}  
  
function move(obj) {  
  var tmp = obj.x + 42;  
  var x = Math.random();  
  x += 1;  
  return tmp + x;  
}  
  
for (var i = 0; i < 0x10000; ++i) {  
  move(new Player());  
}  
  
move(new Wall());  
for (var i = 0; i < 0x10000; ++i) {  
  // 构造对象变化
  move(new Wall());  
}

查看输出信息

out/Release/d8 test.js --allow-natives-syntax --trace-opt
[manually marking 0x1e82326a3779 <JSFunction opt_me (sfi = 0x1e82326a3591)> for non-concurrent optimization]
[compiling method 0x1e82326a3779 <JSFunction opt_me (sfi = 0x1e82326a3591)> using TurboFan]
[optimizing 0x1e82326a3779 <JSFunction opt_me (sfi = 0x1e82326a3591)> - took 8.588, 3.554, 0.157 ms]
xinali@ubuntu:/mnt/hgfs/G/codes/test_turbofan$ ~/v8/v8/out/Release/d8 test_opt_deopt.js --allow-natives-syntax --trace-opt --trace-deopt
# 优化原因: small function
[marking 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> for optimized recompilation, reason: small function, ICs with typeinfo: 7/7 (100%), generic ICs: 0/7 (0%)]
[compiling method 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> using TurboFan]
[optimizing 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> - took 8.261, 2.634, 0.140 ms]
[completed optimizing 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)>]
# 优化原因: hot and stable
[marking 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> for optimized recompilation, reason: hot and stable, ICs with typeinfo: 7/13 (53%), generic ICs: 0/13 (0%)]
[compiling method 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> using TurboFan OSR]
[optimizing 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> - took 4.262, 7.457, 0.311 ms]
# 开始反优化
[deoptimizing (DEOPT soft): begin 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> (opt #1) @6, FP to SP delta: 104, caller sp: 0x7ffee0d4d298]
            ;;; deoptimize at <test_opt_deopt.js:15:6>, Insufficient type feedback for construct
  reading input frame  => bytecode_offset=154, args=1, height=9; inputs:
      0: 0x3e6cd4c238e9 ;  [fp -  16]  0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)>
      1: 0x0660d0f82231 ;  [fp +  16]  0x0660d0f82231 <JSGlobal Object>
      2: 0x3e6cd4c23a59 ;  [fp - 104]  0x3e6cd4c23a59 <ScriptContext[6]>
      3: 0x2954eb702f39 ; (literal  3) 0x2954eb702f39 <Odd Oddball: optimized_out>
      4: 0x2954eb702f39 ; (literal  3) 0x2954eb702f39 <Odd Oddball: optimized_out>
      5: 0x2954eb702f39 ; (literal  3) 0x2954eb702f39 <Odd Oddball: optimized_out>
      6: 0x2954eb702f39 ; (literal  3) 0x2954eb702f39 <Odd Oddball: optimized_out>
      7: 0x3e6cd4c23a99 ; (literal  4) 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)>
      8: 0x0660d0f84819 ; rbx 0x0660d0f84819 <JSFunction Wall (sfi = 0x3e6cd4c236a9)>
      9: 0x2954eb702f39 ; (literal  3) 0x2954eb702f39 <Odd Oddball: optimized_out>
     10: 0x2954eb702f39 ; (literal  3) 0x2954eb702f39 <Odd Oddball: optimized_out>
     11: 0x0660d0f84819 ; rbx 0x0660d0f84819 <JSFunction Wall (sfi = 0x3e6cd4c236a9)>
  translating interpreted frame  => bytecode_offset=154, height=72
    0x7ffee0d4d290: [top + 120] <- 0x0660d0f82231 <JSGlobal Object> ;  stack parameter (input #1)
    -------------------------
    0x7ffee0d4d288: [top + 112] <- 0x7ff4dd5bb9e3 ;  caller's pc
    0x7ffee0d4d280: [top + 104] <- 0x7ffee0d4d2a8 ;  caller's fp
    0x7ffee0d4d278: [top +  96] <- 0x3e6cd4c23a59 <ScriptContext[6]> ;  context
 (input #0)
    0x7ffee0d4d270: [top +  88] <- 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> ;  function
 (input #0)
    0x7ffee0d4d268: [top +  80] <- 0x3e6cd4c237a9 <BytecodeArray[225]> ;  bytecode array
    0x7ffee0d4d260: [top +  72] <- 0x00d300000000 <Smi 211> ;  bytecode offset
    -------------------------
    0x7ffee0d4d258: [top +  64] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #3)
    0x7ffee0d4d250: [top +  56] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #4)
    0x7ffee0d4d248: [top +  48] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #5)
    0x7ffee0d4d240: [top +  40] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #6)
    0x7ffee0d4d238: [top +  32] <- 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> ;  stack parameter (input #7)
    0x7ffee0d4d230: [top +  24] <- 0x0660d0f84819 <JSFunction Wall (sfi = 0x3e6cd4c236a9)> ;  stack parameter (input #8)
    0x7ffee0d4d228: [top +  16] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #9)
    0x7ffee0d4d220: [top +   8] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #10)
    0x7ffee0d4d218: [top +   0] <- 0x0660d0f84819 <JSFunction Wall (sfi = 0x3e6cd4c236a9)> ;  accumulator (input #0)
[deoptimizing (soft): end 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> @6 => node=154, pc=0x7ff4dd5c6b00, caller sp=0x7ffee0d4d298, took 0.712 ms]
[deoptimizing (DEOPT eager): begin 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> (opt #0) @1, FP to SP delta: 24, caller sp: 0x7ffee0d4d220]
            ;;; deoptimize at <test_opt_deopt.js:5:17>, wrong map
  reading input frame move => bytecode_offset=0, args=2, height=5; inputs:
      0: 0x3e6cd4c23a99 ;  [fp -  16]  0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)>
      1: 0x0660d0f82231 ;  [fp +  24]  0x0660d0f82231 <JSGlobal Object>
      2: 0x0fce03c07da9 ; rax 0x0fce03c07da9 <Wall map = 0x2b0118f8c891>
      3: 0x3e6cd4c23a59 ;  [fp -  24]  0x3e6cd4c23a59 <ScriptContext[6]>
      4: 0x2954eb702f39 ; (literal  1) 0x2954eb702f39 <Odd Oddball: optimized_out>
      5: 0x2954eb702f39 ; (literal  1) 0x2954eb702f39 <Odd Oddball: optimized_out>
      6: 0x2954eb702f39 ; (literal  1) 0x2954eb702f39 <Odd Oddball: optimized_out>
      7: 0x2954eb702f39 ; (literal  1) 0x2954eb702f39 <Odd Oddball: optimized_out>
      8: 0x2954eb702f39 ; (literal  1) 0x2954eb702f39 <Odd Oddball: optimized_out>
  translating interpreted frame move => bytecode_offset=0, height=40
    0x7ffee0d4d218: [top +  96] <- 0x0660d0f82231 <JSGlobal Object> ;  stack parameter (input #1)
    0x7ffee0d4d210: [top +  88] <- 0x0fce03c07da9 <Wall map = 0x2b0118f8c891> ;  stack parameter (input #2)
    -------------------------
    0x7ffee0d4d208: [top +  80] <- 0x298fc25967d8 ;  caller's pc
    0x7ffee0d4d200: [top +  72] <- 0x7ffee0d4d280 ;  caller's fp
    0x7ffee0d4d1f8: [top +  64] <- 0x3e6cd4c23a59 <ScriptContext[6]> ;  context
 (input #0)
    0x7ffee0d4d1f0: [top +  56] <- 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> ;  function
 (input #0)
    0x7ffee0d4d1e8: [top +  48] <- 0x3e6cd4c23cc9 <BytecodeArray[38]> ;  bytecode array
    0x7ffee0d4d1e0: [top +  40] <- 0x003900000000 <Smi 57> ;  bytecode offset
    -------------------------
    0x7ffee0d4d1d8: [top +  32] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #4)
    0x7ffee0d4d1d0: [top +  24] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #5)
    0x7ffee0d4d1c8: [top +  16] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #6)
    0x7ffee0d4d1c0: [top +   8] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  stack parameter (input #7)
    0x7ffee0d4d1b8: [top +   0] <- 0x2954eb702f39 <Odd Oddball: optimized_out> ;  accumulator (input #0)
[deoptimizing (eager): end 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> @1 => node=0, pc=0x7ff4dd5c6b00, caller sp=0x7ffee0d4d220, took 0.265 ms]
# 再次开始优化
[marking 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> for optimized recompilation, reason: small function, ICs with typeinfo: 7/7 (100%), generic ICs: 0/7 (0%)]
[compiling method 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> using TurboFan]
[optimizing 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)> - took 1.757, 2.805, 0.191 ms]
[completed optimizing 0x3e6cd4c23a99 <JSFunction move (sfi = 0x3e6cd4c235f9)>]
[compiling method 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> using TurboFan OSR]
[optimizing 0x3e6cd4c238e9 <JSFunction (sfi = 0x3e6cd4c234e9)> - took 4.025, 7.108, 0.273 ms]
  1. 查看优化判断函数
    源码: src/runtime-profiler\.cc
OptimizationReason RuntimeProfiler::ShouldOptimize(JSFunction* function,
                                                   JavaScriptFrame* frame) {
  SharedFunctionInfo* shared = function->shared();
  int ticks = function->feedback_vector()->profiler_ticks();

  // 根据函数生产的ignition code大小判断是否优化
  if (shared->GetBytecodeArray()->length() > kMaxBytecodeSizeForOpt) {
    return OptimizationReason::kDoNotOptimize;
  }

  int ticks_for_optimization =
      kProfilerTicksBeforeOptimization +
      (shared->GetBytecodeArray()->length() / kBytecodeSizeAllowancePerTick);
  // 执行频繁且稳定
  if (ticks >= ticks_for_optimization) {
    return OptimizationReason::kHotAndStable;
  } 
  // JSFunction中的IC没有变化,并且函数较小
  else if (!any_ic_changed_ && shared->GetBytecodeArray()->length() <
                                     kMaxBytecodeSizeForEarlyOpt) {
    // If no IC was patched since the last tick and this function is very
    // small, optimistically optimize it now.
    return OptimizationReason::kSmallFunction;
  } else if (FLAG_trace_opt_verbose) {
    PrintF("[not yet optimizing ");
    function->PrintName();
    PrintF(", not enough ticks: %d/%d and ", ticks,
           kProfilerTicksBeforeOptimization);
    if (any_ic_changed_) {
      PrintF("ICs changed]\n");
    } else {
      PrintF(" too large for small function optimization: %d/%d]\n",
             shared->GetBytecodeArray()->length(), kMaxBytecodeSizeForEarlyOpt);
    }
  }
  return OptimizationReason::kDoNotOptimize;
}
  1. turbofan执行流程

GetOptimizedCode函数分析

MaybeHandle<Code> GetOptimizedCode(Handle<JSFunction> function,
                                   ConcurrencyMode mode,
                                   BailoutId osr_offset = BailoutId::None(),
                                   JavaScriptFrame* osr_frame = nullptr) {
  // 获取基础信息
  Isolate* isolate = function->GetIsolate();
  Handle<SharedFunctionInfo> shared(function->shared(), isolate);

  // Make sure we clear the optimization marker on the function so that we
  // don't try to re-optimize.
  /* 
	  enum class OptimizationMarker {
		  kLogFirstExecution,
		  kNone,
		  kCompileOptimized,
		  kCompileOptimizedConcurrent,
		  kInOptimizationQueue
      };
  */
  // 是否存在优化标志
  if (function->HasOptimizationMarker()) {
    function->ClearOptimizationMarker();
  }

  // 是否调试
  if (isolate->debug()->needs_check_on_function_call()) {
    // Do not optimize when debugger needs to hook into every call.
    return MaybeHandle<Code>();
  }

  Handle<Code> cached_code;
  // 是否已经优化
  if (GetCodeFromOptimizedCodeCache(function, osr_offset)
          .ToHandle(&cached_code)) {
    if (FLAG_trace_opt) {
      PrintF("[found optimized code for ");
      function->ShortPrint();
      if (!osr_offset.IsNone()) {
        PrintF(" at OSR AST id %d", osr_offset.ToInt());
      }
      PrintF("]\n");
    }
    // 直接返回以前优化代码
    return cached_code;
  }

  // Reset profiler ticks, function is no longer considered hot.
  // 优化前设置profiler ticks, 主要是为了不再重复优化
  DCHECK(shared->is_compiled());
  function->feedback_vector()->set_profiler_ticks(0);

  VMState<COMPILER> state(isolate);
  DCHECK(!isolate->has_pending_exception());
  PostponeInterruptsScope postpone(isolate);
  bool has_script = shared->script()->IsScript();
  // BUG(5946): This DCHECK is necessary to make certain that we won't
  // tolerate the lack of a script without bytecode.
  DCHECK_IMPLIES(!has_script, shared->HasBytecodeArray());
  // 构建优化任务
  std::unique_ptr<OptimizedCompilationJob> job(
  // Pipeline创建job,执行亦然
      compiler::Pipeline::NewCompilationJob(isolate, function, has_script));
  OptimizedCompilationInfo* compilation_info = job->compilation_info();

  compilation_info->SetOptimizingForOsr(osr_offset, osr_frame);

  // Do not use TurboFan if we need to be able to set break points.
  // 存在断点,不优化
  if (compilation_info->shared_info()->HasBreakInfo()) {
    compilation_info->AbortOptimization(BailoutReason::kFunctionBeingDebugged);
    return MaybeHandle<Code>();
  }

  // Do not use TurboFan when %NeverOptimizeFunction was applied.
  // 存在NeverOptimizeFunction标志,不优化
  if (shared->optimization_disabled() &&
      shared->disable_optimization_reason() ==
          BailoutReason::kOptimizationDisabledForTest) {
    compilation_info->AbortOptimization(
        BailoutReason::kOptimizationDisabledForTest);
    return MaybeHandle<Code>();
  }

  // Do not use TurboFan if optimization is disabled or function doesn't pass
  // turbo_filter.
  // --turbo-filter=optFunc 选项
  if (!FLAG_opt || !shared->PassesFilter(FLAG_turbo_filter)) {
    compilation_info->AbortOptimization(BailoutReason::kOptimizationDisabled);
    return MaybeHandle<Code>();
  }

  TimerEventScope<TimerEventOptimizeCode> optimize_code_timer(isolate);
  RuntimeCallTimerScope runtimeTimer(isolate,
                                     RuntimeCallCounterId::kOptimizeCode);
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), "V8.OptimizeCode");

  // In case of concurrent recompilation, all handles below this point will be
  // allocated in a deferred handle scope that is detached and handed off to
  // the background thread when we return.
  base::Optional<CompilationHandleScope> compilation;
  if (mode == ConcurrencyMode::kConcurrent) {
    compilation.emplace(isolate, compilation_info);
  }

  // All handles below will be canonicalized.
  CanonicalHandleScope canonical(isolate);

  // Reopen handles in the new CompilationHandleScope.
  compilation_info->ReopenHandlesInNewHandleScope(isolate);

  // 判断是否并发
  if (mode == ConcurrencyMode::kConcurrent) {
    // 将job加入队列后续优化
    if (GetOptimizedCodeLater(job.get(), isolate)) {
      job.release();  // The background recompile job owns this now.

      // Set the optimization marker and return a code object which checks it.
      function->SetOptimizationMarker(OptimizationMarker::kInOptimizationQueue);
      DCHECK(function->IsInterpreted() ||
             (!function->is_compiled() && function->shared()->IsInterpreted()));
      DCHECK(function->shared()->HasBytecodeArray());
      return BUILTIN_CODE(isolate, InterpreterEntryTrampoline);
    }
  } else {
    // 非并发则立刻优化
    if (GetOptimizedCodeNow(job.get(), isolate))
      return compilation_info->code();
  }

  if (isolate->has_pending_exception()) isolate->clear_pending_exception();
  return MaybeHandle<Code>();
}

分析

bool GetOptimizedCodeLater(OptimizedCompilationJob* job, Isolate* isolate) {
  OptimizedCompilationInfo* compilation_info = job->compilation_info();
  if (!isolate->optimizing_compile_dispatcher()->IsQueueAvailable()) {
    if (FLAG_trace_concurrent_recompilation) {
      PrintF("  ** Compilation queue full, will retry optimizing ");
      compilation_info->closure()->ShortPrint();
      PrintF(" later.\n");
    }
    return false;
  }

  if (isolate->heap()->HighMemoryPressure()) {
    if (FLAG_trace_concurrent_recompilation) {
      PrintF("  ** High memory pressure, will retry optimizing ");
      compilation_info->closure()->ShortPrint();
      PrintF(" later.\n");
    }
    return false;
  }

  TimerEventScope<TimerEventRecompileSynchronous> timer(isolate);
  RuntimeCallTimerScope runtimeTimer(
      isolate, RuntimeCallCounterId::kRecompileSynchronous);
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.RecompileSynchronous");

  if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED) return false;
  // 放入队列等待优化
  isolate->optimizing_compile_dispatcher()->QueueForOptimization(job);

  if (FLAG_trace_concurrent_recompilation) {
    PrintF("  ** Queued ");
    compilation_info->closure()->ShortPrint();
    PrintF(" for concurrent optimization.\n");
  }
  return true;
}

分析

bool GetOptimizedCodeNow(OptimizedCompilationJob* job, Isolate* isolate) {
  TimerEventScope<TimerEventRecompileSynchronous> timer(isolate);
  RuntimeCallTimerScope runtimeTimer(
      isolate, RuntimeCallCounterId::kRecompileSynchronous);
  OptimizedCompilationInfo* compilation_info = job->compilation_info();
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
               "V8.RecompileSynchronous");

  if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED ||
      job->ExecuteJob() != CompilationJob::SUCCEEDED ||
      job->FinalizeJob(isolate) != CompilationJob::SUCCEEDED) {
    if (FLAG_trace_opt) {
      PrintF("[aborted optimizing ");
      compilation_info->closure()->ShortPrint();
      PrintF(" because: %s]\n",
             GetBailoutReason(compilation_info->bailout_reason()));
    }
    return false;
  }

  // Success!
  job->RecordCompilationStats();
  DCHECK(!isolate->has_pending_exception());
  InsertCodeIntoOptimizedCodeCache(compilation_info);
  job->RecordFunctionCompilation(CodeEventListener::LAZY_COMPILE_TAG, isolate);
  return true;
}

优化代码
根据job的执行步骤,会有如下三步

job->PrepareJob
job->ExecuteJob
job->FinalizeJob

再经过分析PrepareJob/ExecuteJobFinalizeJob有如下最终实现,并分别分析

PrepareJobImpl分析,主要功能在于开始构图

PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl(
    Isolate* isolate) {
  if (compilation_info()->shared_info()->GetBytecodeArray()->length() >
      kMaxBytecodeSizeForTurbofan) {
    return AbortOptimization(BailoutReason::kFunctionTooBig);
  }

  //...

  data_.set_start_source_position(
      compilation_info()->shared_info()->StartPosition());

  linkage_ = new (compilation_info()->zone()) Linkage(
      Linkage::ComputeIncoming(compilation_info()->zone(), compilation_info()));
  // 构建图
  if (!pipeline_.CreateGraph()) {
    if (isolate->has_pending_exception()) return FAILED;  // Stack overflowed.
    return AbortOptimization(BailoutReason::kGraphBuildingFailed);
  }

  if (compilation_info()->is_osr()) data_.InitializeOsrHelper();

  // Make sure that we have generated the maximal number of deopt entries.
  // This is in order to avoid triggering the generation of deopt entries later
  // during code assembly.
  Deoptimizer::EnsureCodeForMaxDeoptimizationEntries(isolate);

  return SUCCEEDED;
}

构图代码中,会一开始就调用GraphBuilderPhase来构建一个基础的简单图,用于后期各种优化

bool PipelineImpl::CreateGraph() {
  PipelineData* data = this->data_;

  data->BeginPhaseKind("graph creation");

  // ...

  Run<GraphBuilderPhase>();
  RunPrintAndVerify(GraphBuilderPhase::phase_name(), true);

  // Perform function context specialization and inlining (if enabled).
  Run<InliningPhase>();
  RunPrintAndVerify(InliningPhase::phase_name(), true);

  // Remove dead->live edges from the graph.
  Run<EarlyGraphTrimmingPhase>();
  RunPrintAndVerify(EarlyGraphTrimmingPhase::phase_name(), true);

  // Run the type-sensitive lowerings and optimizations on the graph.
  {
    // Determine the Typer operation flags.
    Typer::Flags flags = Typer::kNoFlags;
    if (is_sloppy(info()->shared_info()->language_mode()) &&
        info()->shared_info()->IsUserJavaScript()) {
      // Sloppy mode functions always have an Object for this.
      flags |= Typer::kThisIsReceiver;
    }
    if (IsClassConstructor(info()->shared_info()->kind())) {
      // Class constructors cannot be [[Call]]ed.
      flags |= Typer::kNewTargetIsReceiver;
    }

    // Type the graph and keep the Typer running on newly created nodes within
    // this scope; the Typer is automatically unlinked from the Graph once we
    // leave this scope below.
    Typer typer(isolate(), data->js_heap_broker(), flags, data->graph());
    Run<TyperPhase>(&typer);
    RunPrintAndVerify(TyperPhase::phase_name());

    // Do some hacky things to prepare for the optimization phase.
    // (caching handles, etc.).
    Run<ConcurrentOptimizationPrepPhase>();

    if (FLAG_concurrent_compiler_frontend) {
      data->js_heap_broker()->SerializeStandardObjects();
      Run<CopyMetadataForConcurrentCompilePhase>();
    }

    // Lower JSOperators where we can determine types.
    Run<TypedLoweringPhase>();
    RunPrintAndVerify(TypedLoweringPhase::phase_name());
  }

  data->EndPhaseKind();

  return true;
}

在构图过程中,主要用了以下几个phase,主要是生成初始的基础图,为后续优化做准备

  1. GraphBuilderPhase:
    • 创建初始图形表示(graph representation)。
    • 这是将字节码转换为中间表示形式的阶段,生成一个对应于 JavaScript 函数的节点图。
  2. InliningPhase:
    • 进行函数内联。
    • 如果一个函数调用了另一个小的函数,内联可能会将被调用的函数的代码直接插入到调用者的代码中,从而避免函数调用的开销。
  3. EarlyGraphTrimmingPhase:
    • 从图中移除死亡到生存的边。
    • 这可以帮助简化图,并减少后续 Phases 的工作量。
  4. TyperPhase:
    • 对图中的节点进行类型分析。
    • 这为后续的优化 Phases 提供了有关值类型的信息,从而使这些 Phases 能够做出更加明智的决策。
  5. ConcurrentOptimizationPrepPhase:
    • 为并发优化阶段做准备。
    • 这包括缓存句柄和其他准备工作。
  6. CopyMetadataForConcurrentCompilePhase (只在启用 FLAG_concurrent_compiler_frontend 时运行):
    • 为并发编译阶段复制元数据。
    • 这确保了在并发编译过程中不会出现数据不一致的情况。
  7. TypedLoweringPhase:
    • 根据已知的类型信息降低JSOperators。
    • 例如,如果知道某个操作涉及到整数,那么可以使用专门的整数操作替代更一般的操作。

构图之后进行优化

bool PipelineImpl::OptimizeGraph(Linkage* linkage) {
  PipelineData* data = this->data_;

  data->BeginPhaseKind("lowering");

  // Perform simplified lowering. This has to run w/o the Typer decorator,
  // because we cannot compute meaningful types anyways, and the computed types
  // might even conflict with the representation/truncation logic.
  Run<SimplifiedLoweringPhase>();
  RunPrintAndVerify(SimplifiedLoweringPhase::phase_name(), true);

  // From now on it is invalid to look at types on the nodes, because the types
  // on the nodes might not make sense after representation selection due to the
  // way we handle truncations; if we'd want to look at types afterwards we'd
  // essentially need to re-type (large portions of) the graph.

  // In order to catch bugs related to type access after this point, we now
  // remove the types from the nodes (currently only in Debug builds).
#ifdef DEBUG
  Run<UntyperPhase>();
  RunPrintAndVerify(UntyperPhase::phase_name(), true);
#endif

  // Run generic lowering pass.
  Run<GenericLoweringPhase>();
  RunPrintAndVerify(GenericLoweringPhase::phase_name(), true);

  data->BeginPhaseKind("block building");

  // Run early optimization pass.
  Run<EarlyOptimizationPhase>();
  RunPrintAndVerify(EarlyOptimizationPhase::phase_name(), true);

  Run<EffectControlLinearizationPhase>();
  RunPrintAndVerify(EffectControlLinearizationPhase::phase_name(), true);

  if (FLAG_turbo_store_elimination) {
    Run<StoreStoreEliminationPhase>();
    RunPrintAndVerify(StoreStoreEliminationPhase::phase_name(), true);
  }

  // Optimize control flow.
  if (FLAG_turbo_cf_optimization) {
    Run<ControlFlowOptimizationPhase>();
    RunPrintAndVerify(ControlFlowOptimizationPhase::phase_name(), true);
  }

  // Optimize memory access and allocation operations.
  Run<MemoryOptimizationPhase>();
  // TODO(jarin, rossberg): Remove UNTYPED once machine typing works.
  RunPrintAndVerify(MemoryOptimizationPhase::phase_name(), true);

  // Lower changes that have been inserted before.
  Run<LateOptimizationPhase>();
  // TODO(jarin, rossberg): Remove UNTYPED once machine typing works.
  RunPrintAndVerify(LateOptimizationPhase::phase_name(), true);

  data->source_positions()->RemoveDecorator();
  if (data->info()->trace_turbo_json_enabled()) {
    data->node_origins()->RemoveDecorator();
  }

  ComputeScheduledGraph();

  return SelectInstructions(linkage);
}

在PipelineImpl::OptimizeGraph(Linkage* linkage)函数中,V8使用了一系列的Phases来优化中间表示的图。以下是这个函数中的各个Phases及其对应的作用:

  1. LoopPeelingPhase 或 LoopExitEliminationPhase(取决于循环剥离是否启用):
    • LoopPeelingPhase: 对于常数循环条件,将循环的第一次迭代从循环中"剥离"出来。
    • LoopExitEliminationPhase: 优化掉那些永远不会被执行的循环退出点。
  2. LoadEliminationPhase(如果 FLAG_turbo_load_elimination 启用):
    • 消除不必要的加载操作,特别是如果数据已经在寄存器或栈中可用。
  3. EscapeAnalysisPhase(如果 FLAG_turbo_escape 启用):
    • 分析对象的生存周期,以确定哪些对象不会逃逸到其创建范围之外。这可能允许更有效的内存管理和优化。
  4. SimplifiedLoweringPhase:
    • 在不考虑特定的机器语义的情况下,简化和降低图的部分节点。
  5. UntyperPhase(只在 Debug 构建中):
    • 从图中的节点中移除类型信息。这是为了确保在某个点之后不会再访问这些类型,并帮助捕获此后的类型访问相关的错误。
  6. GenericLoweringPhase:
    • 对图进行通用的降低,使其更接近于最终的机器代码。
  7. EarlyOptimizationPhase:
    • 执行一些早期的优化,这些优化通常在图的结构变得更复杂之前进行。
  8. EffectControlLinearizationPhase:
    • 线性化图中的效果和控制流,为后续的阶段提供一个更结构化的图。
  9. StoreStoreEliminationPhase(如果 FLAG_turbo_store_elimination 启用):
    • 消除连续的、冗余的存储操作。
  10. ControlFlowOptimizationPhase(如果 FLAG_turbo_cf_optimization 启用):
    • 优化图中的控制流。
  11. MemoryOptimizationPhase:
    • 优化内存访问和分配操作。
  12. LateOptimizationPhase:
    • 执行一些后期的优化,这些优化通常在图的大部分已经稳定下来后进行。

在每个阶段后面,都有一个RunPrintAndVerify调用,用于在开发过程中验证和跟踪图的状态。
最后,ComputeScheduledGraph()负责计算调度的图,而SelectInstructions(linkage)则选择机器指令来完成编译过程。
这个函数中的Phases负责对图进行各种优化,使得最后生成的机器代码更加高效。

重点phase分析

以上只是简单阐述了部分phase,现在来详细分析一下跟本文有关的phase

TyperPhase

其定义

struct TyperPhase {
  static const char* phase_name() { return "typer"; }

  void Run(PipelineData* data, Zone* temp_zone, Typer* typer) {
    NodeVector roots(temp_zone);
    data->jsgraph()->GetCachedNodes(&roots);
    LoopVariableOptimizer induction_vars(data->jsgraph()->graph(),
                                         data->common(), temp_zone);
    if (FLAG_turbo_loop_variable) induction_vars.Run();
    // 调用下面的Run
    typer->Run(roots, &induction_vars);
  }
};

void Typer::Run(const NodeVector& roots,
                LoopVariableOptimizer* induction_vars) {
  if (induction_vars != nullptr) {
    induction_vars->ChangeToInductionVariablePhis();
  }
  // 构建一个reducer
  Visitor visitor(this, induction_vars);
  GraphReducer graph_reducer(zone(), graph());
  // 将reducer加入到GraphReducer中
  graph_reducer.AddReducer(&visitor);
  // 针对每个节点应用reducer
  for (Node* const root : roots) graph_reducer.ReduceNode(root);
  graph_reducer.ReduceGraph();

  if (induction_vars != nullptr) {
    induction_vars->ChangeToPhisAndInsertGuards();
  }
}

// 关于ReduceNode和ReduceGraph
// src/compiler/graph-reducer.cc/h
void GraphReducer::ReduceNode(Node* node) {
  DCHECK(stack_.empty());
  DCHECK(revisit_.empty());
  Push(node);
  for (;;) {
    if (!stack_.empty()) {
      // Process the node on the top of the stack, potentially pushing more or
      // popping the node off the stack.
      ReduceTop(); // 调用Reduce => node->reduce
    } else if (!revisit_.empty()) {
      // If the stack becomes empty, revisit any nodes in the revisit queue.
      Node* const node = revisit_.front();
      revisit_.pop();
      if (state_.Get(node) == State::kRevisit) {
        // state can change while in queue.
        Push(node);
      }
    } else {
      // Run all finalizers.
      for (Reducer* const reducer : reducers_) reducer->Finalize();

      // Check if we have new nodes to revisit.
      if (revisit_.empty()) break;
    }
  }
  DCHECK(revisit_.empty());
  DCHECK(stack_.empty());
}


void GraphReducer::ReduceGraph() { ReduceNode(graph()->end()); }

我们通过设置断点和日志来看一下ReduceNode最终调用了哪些Reducer

for (Reducer* const reducer : reducers_) {
	V8_LOG("reducer name: %s", reducer->reducer_name());
	// V8_STACK();
	reducer->Finalize();
}

查看一下其中的输出

...
../../src/compiler/graph-reducer.cc(70):ReduceNode -> reducer name: JSNativeContextSpecialization
../../src/compiler/graph-reducer.cc(70):ReduceNode -> reducer name: JSContextSpecialization
../../src/compiler/graph-reducer.cc(70):ReduceNode -> reducer name: JSIntrinsicLowering
../../src/compiler/graph-reducer.cc(70):ReduceNode -> reducer name: Typer
../../src/compiler/graph-reducer.cc(70):ReduceNode -> reducer name: JSCallReducer
...

根据分析代码可以发现,不同的Reducer会调用其对应的Reduce来处理各种节点,其中Reducer为Typer时,调用的是Visitor,调用栈如下

V8(STACK): Reduce in Visitor
==== C stack trace ===============================

    /home/xinali/v8/v8/out/Release/./libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7fdaf885465e]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::Typer::Visitor::Reduce(v8::internal::compiler::Node*)+0x33) [0x7fdaf73b7b83]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::GraphReducer::Reduce(v8::internal::compiler::Node*)+0x29d) [0x7fdaf7141cad]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::GraphReducer::ReduceTop()+0x459) [0x7fdaf7141789]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::GraphReducer::ReduceNode(v8::internal::compiler::Node*)+0x1ce) [0x7fdaf7140cae]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::GraphReducer::ReduceGraph()+0x2d) [0x7fdaf71419fd]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::Typer::Run(v8::internal::ZoneVector<v8::internal::compiler::Node*> const&, v8::internal::compiler::LoopVariableOptimizer*)+0x220) [0x7fdaf73b2020]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::TyperPhase::Run(v8::internal::compiler::PipelineData*, v8::internal::Zone*, v8::internal::compiler::Typer*)+0xa8) [0x7fdaf72fa748]
    /home/xinali/v8/v8/out/Release/./libv8.so(void v8::internal::compiler::PipelineImpl::Run<v8::internal::compiler::TyperPhase, v8::internal::compiler::Typer*>(v8::internal::compiler::Typer*)+0x5c) [0x7fdaf72f42dc]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::PipelineImpl::CreateGraph()+0x555) [0x7fdaf72e9065]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::compiler::PipelineCompilationJob::PrepareJobImpl(v8::internal::Isolate*)+0x2a1) [0x7fdaf72e8a81]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::OptimizedCompilationJob::PrepareJob(v8::internal::Isolate*)+0x3a1) [0x7fdaf703b061]
    /home/xinali/v8/v8/out/Release/./libv8.so(+0x10a87e5) [0x7fdaf70457e5]
    /home/xinali/v8/v8/out/Release/./libv8.so(+0x10a0ae0) [0x7fdaf703dae0]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::Compiler::CompileOptimized(v8::internal::Handle<v8::internal::JSFunction>, v8::internal::ConcurrencyMode)+0xba) [0x7fdaf703dd8a]
    /home/xinali/v8/v8/out/Release/./libv8.so(+0x1b72e5c) [0x7fdaf7b0fe5c]
    /home/xinali/v8/v8/out/Release/./libv8.so(v8::internal::Runtime_CompileOptimized_NotConcurrent(int, v8::internal::Object**, v8::internal::Isolate*)+0x107) [0x7fdaf7b0fa97]
    /home/xinali/v8/v8/out/Release/./libv8.so(+0x2438515) [0x7fdaf83d5515]

...

具体分析Visitor的Reduce

class Typer::Visitor : public Reducer {
 public:
  explicit Visitor(Typer* typer, LoopVariableOptimizer* induction_vars)
      : typer_(typer),
        induction_vars_(induction_vars),
        weakened_nodes_(typer->zone()) {}

  const char* reducer_name() const override { return "Typer"; }

  Reduction Reduce(Node* node) override {
    V8_STACK();
    if (node->op()->ValueOutputCount() == 0) return NoChange();
    switch (node->opcode())
// 根据节点的opcode类型,更新其中type
#define DECLARE_CASE(x) \
  case IrOpcode::k##x:  \
    return UpdateType(node, TypeBinaryOp(node, x##Typer));
      JS_SIMPLE_BINOP_LIST(DECLARE_CASE)
#undef DECLARE_CASE

#define DECLARE_CASE(x) \
  case IrOpcode::k##x:  \
    return UpdateType(node, Type##x(node));
      DECLARE_CASE(Start)
      DECLARE_CASE(IfException)
      // VALUE_OP_LIST without JS_SIMPLE_BINOP_LIST:
      COMMON_OP_LIST(DECLARE_CASE) // 常数类型
      SIMPLIFIED_COMPARE_BINOP_LIST(DECLARE_CASE)
      SIMPLIFIED_OTHER_OP_LIST(DECLARE_CASE)
      JS_SIMPLE_UNOP_LIST(DECLARE_CASE)
      JS_OBJECT_OP_LIST(DECLARE_CASE)
      JS_CONTEXT_OP_LIST(DECLARE_CASE)
      JS_OTHER_OP_LIST(DECLARE_CASE)
#undef DECLARE_CASE
// ...
}

#define COMMON_OP_LIST(V) \
  CONSTANT_OP_LIST(V)     \
  INNER_OP_LIST(V)        \
  V(Unreachable)          \
  V(DeadValue)            \
  V(Dead)

// Opcodes for constant operators.
#define CONSTANT_OP_LIST(V)   \
  V(Int32Constant)            \
  V(Int64Constant)            \
  V(Float32Constant)          \
  V(Float64Constant)          \
  V(ExternalConstant)         \
  V(NumberConstant)           \
  V(PointerConstant)          \
  V(HeapConstant)             \
  V(RelocatableInt32Constant) \
  V(RelocatableInt64Constant)

根据上面的代码可以发现,当opcode为NumberConstant时,会调用TypeNumberConstant,在该函数中会调用NewConstant用来构造一个整数,并且是一个范围

Type Typer::Visitor::TypeNumberConstant(Node* node) {
  double number = OpParameter<double>(node->op());
  return Type::NewConstant(number, zone());
}

Type Type::NewConstant(double value, Zone* zone) {
  if (RangeType::IsInteger(value)) {
    return Range(value, value, zone); // value=2 => Range(2, 2, zone);
  } else if (IsMinusZero(value)) {
    return Type::MinusZero();
  } else if (std::isnan(value)) {
    return Type::NaN();
  }

  DCHECK(OtherNumberConstantType::IsOtherNumberConstant(value));
  return OtherNumberConstant(value, zone);
}

通过调用测试代码,会在v8的测试工具中看到Constant确实是有Range的,注意调用时记得使用--trace-opt

Type Typer::Visitor::TypePhi(Node* node) {
  int arity = node->op()->ValueInputCount();
  Type type = Operand(node, 0);
  for (int i = 1; i < arity; ++i) {
    type = Type::Union(type, Operand(node, i), zone());
  }
  return type;
}

文章中提到了SSA相关的内容,以前接触的少,所以搜了一些资料

在V8引擎的 typer.cc 中,TypePhi 函数使用 Type::Union 方法来合并不同路径上的类型,这是因为在SSA(静态单一赋值)形式下,Phi节点表示一个控制流合并点,其中的变量可能具有来自不同前驱路径的多个不同类型。
理解这一点的关键在于理解Phi节点的作用和 Type::Union 方法的功能:

  1. Phi 节点的作用:Phi节点用于表示程序中控制流的合并点,如if-else或循环结构之后。在这些点上,一个变量可能有来自多个不同执行路径的不同值,因此它们可能具有不同的类型。
  2. Union 方法的功能:Type::Union 方法用于创建一个新类型,它是其输入类型的并集。这意味着这个新类型可以接受任何一个输入类型的值。在 Phi 节点的上下文中,这是必要的,因为根据程序的不同执行路径,该变量可能具有不同的类型。
  3. TypePhi 函数的逻辑:在 TypePhi 函数中,首先获取 Phi 节点的第一个操作数的类型,然后遍历所有其他操作数(即所有可能的输入值)。对于每个操作数,它使用 Type::Union 来合并当前累积的类型和新操作数的类型。这样做是为了确保结果类型能够覆盖所有可能的输入类型,反映了在控制流合并点变量可能具有的所有类型。
    例如,如果一个Phi节点有两个输入,一个是整数类型,另一个是浮点类型,那么 Type::Union 将会产生一个能够包含整数和浮点数的类型。这对于后续的优化和代码生成非常重要,因为编译器需要知道变量在任何给定点可能的所有类型,以便生成正确和高效的代码
    总的来说,TypePhi 函数中使用 Type::Union 的原因是为了正确处理 Phi 节点在不同执行路径中可能具有的不同类型,并确保编译器能够对这些情况进行准确的优化。

根据其中的测试代码

function test_phi(b) {
  let x = 10;
  if (b == "foo") {
    x = 5;
  }
  let y = x + 2;
  y = y + 1000;
  y = y * 2;
  return y;
}


test_phi("shit");
%OptimizeFunctionOnNextCall(test_phi);
test_phi("foo");

测试~/v8/v8/out/Release/d8 --allow-natives-syntax --trace-opt --trace-turbo test_typephi.js
结果

Image

SimplifiedLoweringPhase

查看SimplifiedLoweringPhase

Run<SimplifiedLoweringPhase>();
  RunPrintAndVerify(SimplifiedLoweringPhase::phase_name(), true);

在该Phase中,我们只关注其中CheckBounds节点的优化问题

CheckBounds分析

测试代码1

function test_checkbounds() {
	// 局部变量
    var arr = [1.1, 2.2];
    var x = 1;
    return arr[x];
}

// 多次调用,自动优化
for (var i = 0; i < 0x20000; i++) {
    test_checkbounds();
}
print(test_checkbounds());

测试代码2

// 外部变量
var arr = [1.1, 2.2];
function test_checkbounds2() {
    var x = 1;
    return arr[x];
}
for (var i = 0; i < 0x20000; i++) {
    test_checkbounds2();
}

print(test_checkbounds2());

在v8源码simplified-lowering.cc中的CheckBounds

//...
case IrOpcode::kCheckBounds: {
	// V8_STACK();
	V8_LOG("checkbounds");
	const CheckParameters& p = CheckParametersOf(node->op());
	Type index_type = TypeOf(node->InputAt(0));
	Type length_type = TypeOf(node->InputAt(1));
	if (index_type.Is(Type::Integral32OrMinusZero())) {
	  // Map -0 to 0, and the values in the [-2^31,-1] range to the
	  // [2^31,2^32-1] range, which will be considered out-of-bounds
	  // as well, because the {length_type} is limited to Unsigned31.
	  V8_LOG("index_type correct");
	  VisitBinop(node, UseInfo::TruncatingWord32(),
				 MachineRepresentation::kWord32);
	  if (lower() && lowering->poisoning_level_ ==
						 PoisoningMitigationLevel::kDontPoison) {
		V8_LOG("lower correct");
		if (index_type.IsNone()) {
		  V8_LOG("index_type is none");
		}
		if (length_type.IsNone()) {
		  V8_LOG("length_type is none");
		}
		V8_LOG("index_type max: %lf", index_type.Max());
		V8_LOG("length_type min: %lf", length_type.Min());
		if (index_type.IsNone() || length_type.IsNone() ||
			(index_type.Min() >= 0.0 &&
			 index_type.Max() < length_type.Min())) {
		  V8_LOG("index_type range correct");
		  // The bounds check is redundant if we already know that
		  // the index is within the bounds of [0.0, length[.
		  V8_LOG("remove checkbounds node");
		  // 移除CheckBounds节点
		  DeferReplacement(node, node->InputAt(0));
		}
	  }
	}
//...

执行结果

# 局部变量结果
xinali@ubuntu:/mnt/hgfs/G/codes/test_turbofan$ ~/v8/v8/out/Release/d8 --allow-natives-syntax --trace-opt --trace-turbo test_checkbounds.js 
Concurrent recompilation has been disabled for tracing.
[marking 0x3516354236e9 <JSFunction (sfi = 0x3516354234b1)> for optimized recompilation, reason: small function, ICs with typeinfo: 7/9 (77%), generic ICs: 0/9 (0%)]
[marking 0x3516354237f9 <JSFunction test_checkbounds (sfi = 0x351635423581)> for optimized recompilation, reason: small function, ICs with typeinfo: 1/1 (100%), generic ICs: 0/1 (0%)]
[compiling method 0x3516354237f9 <JSFunction test_checkbounds (sfi = 0x351635423581)> using TurboFan]
---------------------------------------------------
Begin compiling method test_checkbounds using Turbofan

../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2418): VisitNode -> lower correct 
../../src/compiler/simplified-lowering.cc(2425): VisitNode -> index_type max: 1.000000 
../../src/compiler/simplified-lowering.cc(2426): VisitNode -> length_type min: 2.000000 
../../src/compiler/simplified-lowering.cc(2430): VisitNode -> index_type range correct 
# 移除CheckBounds节点
../../src/compiler/simplified-lowering.cc(2433): VisitNode -> remove checkbounds node ---------------------------------------------------
Finished compiling method test_checkbounds using Turbofan
[optimizing 0x3516354237f9 <JSFunction test_checkbounds (sfi = 0x351635423581)> - took 45.743, 122.136, 8.163 ms]
[compiling method 0x3516354236e9 <JSFunction (sfi = 0x3516354234b1)> using TurboFan OSR]
---------------------------------------------------
Begin compiling method  using Turbofan

../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2418): VisitNode -> lower correct 
../../src/compiler/simplified-lowering.cc(2425): VisitNode -> index_type max: 1.000000 
../../src/compiler/simplified-lowering.cc(2426): VisitNode -> length_type min: 2.000000 
../../src/compiler/simplified-lowering.cc(2430): VisitNode -> index_type range correct 
../../src/compiler/simplified-lowering.cc(2433): VisitNode -> remove checkbounds node ---------------------------------------------------
Finished compiling method  using Turbofan
[optimizing 0x3516354236e9 <JSFunction (sfi = 0x3516354234b1)> - took 81.837, 297.907, 18.925 ms]
2.2

# 全局变量结果
xinali@ubuntu:/mnt/hgfs/G/codes/test_turbofan$ ~/v8/v8/out/Release/d8 --allow-natives-syntax --trace-opt --trace-turbo test_checkbounds2.js 
Concurrent recompilation has been disabled for tracing.
[marking 0x2cf3aa8a38d1 <JSFunction test_checkbounds2 (sfi = 0x2cf3aa8a35a9)> for optimized recompilation, reason: small function, ICs with typeinfo: 2/2 (100%), generic ICs: 0/2 (0%)]
[compiling method 0x2cf3aa8a38d1 <JSFunction test_checkbounds2 (sfi = 0x2cf3aa8a35a9)> using TurboFan]
---------------------------------------------------
Begin compiling method test_checkbounds2 using Turbofan

../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2418): VisitNode -> lower correct 
../../src/compiler/simplified-lowering.cc(2425): VisitNode -> index_type max: 1.000000
# 全局变量中length_type最小值始终为0
../../src/compiler/simplified-lowering.cc(2426): VisitNode -> length_type min: 0.000000 ---------------------------------------------------
Finished compiling method test_checkbounds2 using Turbofan
[optimizing 0x2cf3aa8a38d1 <JSFunction test_checkbounds2 (sfi = 0x2cf3aa8a35a9)> - took 43.297, 158.627, 15.702 ms]
[marking 0x2cf3aa8a3779 <JSFunction (sfi = 0x2cf3aa8a34b9)> for optimized recompilation, reason: hot and stable, ICs with typeinfo: 9/11 (81%), generic ICs: 0/11 (0%)]
[compiling method 0x2cf3aa8a3779 <JSFunction (sfi = 0x2cf3aa8a34b9)> using TurboFan OSR]
---------------------------------------------------
Begin compiling method  using Turbofan

../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2405): VisitNode -> checkbounds 
../../src/compiler/simplified-lowering.cc(2413): VisitNode -> index_type correct 
../../src/compiler/simplified-lowering.cc(2418): VisitNode -> lower correct 
../../src/compiler/simplified-lowering.cc(2425): VisitNode -> index_type max: 1.000000 
../../src/compiler/simplified-lowering.cc(2426): VisitNode -> length_type min: 0.000000 ---------------------------------------------------
Finished compiling method  using Turbofan
[optimizing 0x2cf3aa8a3779 <JSFunction (sfi = 0x2cf3aa8a34b9)> - took 63.509, 222.435, 31.153 ms]
2.2

局部结果:CheckBounds节点被删除

Image

全局结果:CheckBounds节点没有被删除

Image

google ctf 2018 final just in time

看了上面的TurboFan相关的内容,现在来看一个google的ctf来加深对上面内容的理解

看一下v8相关的patch

diff --git a/BUILD.gn b/BUILD.gn
index c6a58776cd..14c56d2910 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1699,6 +1699,8 @@ v8_source_set("v8_base") {
     "src/compiler/dead-code-elimination.cc",
     "src/compiler/dead-code-elimination.h",
     "src/compiler/diamond.h",
+    "src/compiler/duplicate-addition-reducer.cc",
+    "src/compiler/duplicate-addition-reducer.h",
     "src/compiler/effect-control-linearizer.cc",
     "src/compiler/effect-control-linearizer.h",
     "src/compiler/escape-analysis-reducer.cc",
diff --git a/src/compiler/duplicate-addition-reducer.cc b/src/compiler/duplicate-addition-reducer.cc
new file mode 100644
index 0000000000..59e8437f3d
--- /dev/null
+++ b/src/compiler/duplicate-addition-reducer.cc
@@ -0,0 +1,71 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "src/compiler/duplicate-addition-reducer.h"
+
+#include "src/compiler/common-operator.h"
+#include "src/compiler/graph.h"
+#include "src/compiler/node-properties.h"
+
+namespace v8 {
+namespace internal {
+namespace compiler {
+
+DuplicateAdditionReducer::DuplicateAdditionReducer(Editor* editor, Graph* graph,
+                     CommonOperatorBuilder* common)
+    : AdvancedReducer(editor),
+      graph_(graph), common_(common) {}
+
+Reduction DuplicateAdditionReducer::Reduce(Node* node) {
+  switch (node->opcode()) {
+    case IrOpcode::kNumberAdd:
+      return ReduceAddition(node);
+    default:
+      return NoChange();
+  }
+}
+
+Reduction DuplicateAdditionReducer::ReduceAddition(Node* node) {
+  DCHECK_EQ(node->op()->ControlInputCount(), 0);
+  DCHECK_EQ(node->op()->EffectInputCount(), 0);
+  DCHECK_EQ(node->op()->ValueInputCount(), 2);
+
+  Node* left = NodeProperties::GetValueInput(node, 0);
+  if (left->opcode() != node->opcode()) {
+    return NoChange();
+  }
+
+  Node* right = NodeProperties::GetValueInput(node, 1);
+  if (right->opcode() != IrOpcode::kNumberConstant) {
+    return NoChange();
+  }
+
+  Node* parent_left = NodeProperties::GetValueInput(left, 0);
+  Node* parent_right = NodeProperties::GetValueInput(left, 1);
+  if (parent_right->opcode() != IrOpcode::kNumberConstant) {
+    return NoChange();
+  }
+
+  double const1 = OpParameter<double>(right->op());
+  double const2 = OpParameter<double>(parent_right->op());
+  Node* new_const = graph()->NewNode(common()->NumberConstant(const1+const2));
+
+  NodeProperties::ReplaceValueInput(node, parent_left, 0);
+  NodeProperties::ReplaceValueInput(node, new_const, 1);
+
+  return Changed(node);
+}
+
+}  // namespace compiler
+}  // namespace internal
+}  // namespace v8
diff --git a/src/compiler/duplicate-addition-reducer.h b/src/compiler/duplicate-addition-reducer.h
new file mode 100644
index 0000000000..7285f1ae3e
--- /dev/null
+++ b/src/compiler/duplicate-addition-reducer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef V8_COMPILER_DUPLICATE_ADDITION_REDUCER_H_
+#define V8_COMPILER_DUPLICATE_ADDITION_REDUCER_H_
+
+#include "src/base/compiler-specific.h"
+#include "src/compiler/graph-reducer.h"
+#include "src/globals.h"
+#include "src/machine-type.h"
+
+namespace v8 {
+namespace internal {
+namespace compiler {
+
+// Forward declarations.
+class CommonOperatorBuilder;
+class Graph;
+
+class V8_EXPORT_PRIVATE DuplicateAdditionReducer final
+    : public NON_EXPORTED_BASE(AdvancedReducer) {
+ public:
+  DuplicateAdditionReducer(Editor* editor, Graph* graph,
+                      CommonOperatorBuilder* common);
+  ~DuplicateAdditionReducer() final {}
+
+  const char* reducer_name() const override { return "DuplicateAdditionReducer"; }
+
+  Reduction Reduce(Node* node) final;
+
+ private:
+  Reduction ReduceAddition(Node* node);
+
+  Graph* graph() const { return graph_;}
+  CommonOperatorBuilder* common() const { return common_; };
+
+  Graph* const graph_;
+  CommonOperatorBuilder* const common_;
+
+  DISALLOW_COPY_AND_ASSIGN(DuplicateAdditionReducer);
+};
+
+}  // namespace compiler
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_COMPILER_DUPLICATE_ADDITION_REDUCER_H_
diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc
index 5717c70348..8cca161ad5 100644
--- a/src/compiler/pipeline.cc
+++ b/src/compiler/pipeline.cc
@@ -27,6 +27,7 @@
 #include "src/compiler/constant-folding-reducer.h"
 #include "src/compiler/control-flow-optimizer.h"
 #include "src/compiler/dead-code-elimination.h"
+#include "src/compiler/duplicate-addition-reducer.h"
 #include "src/compiler/effect-control-linearizer.h"
 #include "src/compiler/escape-analysis-reducer.h"
 #include "src/compiler/escape-analysis.h"
@@ -1301,6 +1302,8 @@ struct TypedLoweringPhase {
                                data->jsgraph()->Dead());
     DeadCodeElimination dead_code_elimination(&graph_reducer, data->graph(),
                                               data->common(), temp_zone);
+    DuplicateAdditionReducer duplicate_addition_reducer(&graph_reducer, data->graph(),
+                                              data->common());
     JSCreateLowering create_lowering(&graph_reducer, data->dependencies(),
                                      data->jsgraph(), data->js_heap_broker(),
                                      data->native_context(), temp_zone);
@@ -1318,6 +1321,7 @@ struct TypedLoweringPhase {
                                          data->js_heap_broker(), data->common(),
                                          data->machine(), temp_zone);
     AddReducer(data, &graph_reducer, &dead_code_elimination);
+    AddReducer(data, &graph_reducer, &duplicate_addition_reducer);
     AddReducer(data, &graph_reducer, &create_lowering);
     AddReducer(data, &graph_reducer, &constant_folding_reducer);
     AddReducer(data, &graph_reducer, &typed_optimization);

应用patch

git apply xxx.patch

编译运行

测试代码

function test_dup_add(f) {
    var x = f ? 1.1 : 2.2;
    var y = x + 1 + 1;
}
for (var i = 0; i < 0x2000; i++) {
    test_dup_add(false);
    test_dup_add(true);
}

优化前type lowering

Image

对应的typer lowering优化

Image

chatgpt相关猜测

在缺乏 duplicate-addition-reducer.cc 文件的具体实现细节的情况下,我可以根据常见的编译器优化技术,大致分析 DuplicateAdditionReducer 可能在 V8 JavaScript 引擎中的作用:

DuplicateAdditionReducer 可能的作用

  1. 消除冗余计算:

    • 这个优化器可能被设计用来消除重复的加法操作。例如,如果同一个加法操作(如 a + b)在操作数 a 和 b 不变的情况下多次执行,优化器可能通过一次计算并重用结果来进行优化。
  2. 公共子表达式消除:

    • 这是编译器中的一种标准优化技术。如果表达式 a + b 被计算了不止一次,且在这些计算之间 a 和 b 的值没有改变,编译器可以消除重复的计算,只保留一次计算。
  3. 重排序和合并操作:

    • 优化器可能会重新排列加法操作以提高效率,尤其是如果能将它们合并成更简单的形式(比如将 a + a + a 转换为 3 * a)。
  4. 常量折叠和传播:

    • 如果优化器识别出涉及常量的加法,它可以在编译时预先计算这些表达式。例如,将 x + 0 替换为 x 或将 3 + 4 替换为 7。

V8 JavaScript 引擎的背景

在 V8 引擎的背景下,V8 引擎将 JavaScript 编译为机器代码,这种优化对于性能至关重要。作为一种动态语言,JavaScript 可以在即时(JIT)编译过程中从这类优化中获益匪浅。

结论

在没有 duplicate-addition-reducer.cc 文件实际代码的情况下,这种分析是推测性的,基于一般的编译器优化策略。DuplicateAdditionReducer 在 V8 的背景下可能遵循类似的原则,通过减少不必要的计算和简化表达式来优化 JavaScript 代码执行。要准确了解细节,需要直接检查 duplicate-addition-reducer.cc 文件的实现。

参考

浅析 V8-turboFan
v8的JIT边界检查(CheckBounds)消除的利用
google 2018 final just in time

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions