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编译优化过程
- 生成
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查看具体结构
- 代码优化
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]
- 跟踪优化与反优化
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]
- 查看优化判断函数
源码: 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;
}
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,主要是生成初始的基础图,为后续优化做准备
- GraphBuilderPhase:
- 创建初始图形表示(
graph representation)。
- 这是将字节码转换为中间表示形式的阶段,生成一个对应于
JavaScript 函数的节点图。
- InliningPhase:
- 进行函数内联。
- 如果一个函数调用了另一个小的函数,内联可能会将被调用的函数的代码直接插入到调用者的代码中,从而避免函数调用的开销。
- EarlyGraphTrimmingPhase:
- 从图中移除死亡到生存的边。
- 这可以帮助简化图,并减少后续 Phases 的工作量。
- TyperPhase:
- 对图中的节点进行类型分析。
- 这为后续的优化 Phases 提供了有关值类型的信息,从而使这些 Phases 能够做出更加明智的决策。
- ConcurrentOptimizationPrepPhase:
- 为并发优化阶段做准备。
- 这包括缓存句柄和其他准备工作。
- CopyMetadataForConcurrentCompilePhase (只在启用
FLAG_concurrent_compiler_frontend 时运行):
- 为并发编译阶段复制元数据。
- 这确保了在并发编译过程中不会出现数据不一致的情况。
- 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及其对应的作用:
- LoopPeelingPhase 或 LoopExitEliminationPhase(取决于循环剥离是否启用):
LoopPeelingPhase: 对于常数循环条件,将循环的第一次迭代从循环中"剥离"出来。
LoopExitEliminationPhase: 优化掉那些永远不会被执行的循环退出点。
- LoadEliminationPhase(如果
FLAG_turbo_load_elimination 启用):
- 消除不必要的加载操作,特别是如果数据已经在寄存器或栈中可用。
- EscapeAnalysisPhase(如果
FLAG_turbo_escape 启用):
- 分析对象的生存周期,以确定哪些对象不会逃逸到其创建范围之外。这可能允许更有效的内存管理和优化。
- SimplifiedLoweringPhase:
- 在不考虑特定的机器语义的情况下,简化和降低图的部分节点。
- UntyperPhase(只在 Debug 构建中):
- 从图中的节点中移除类型信息。这是为了确保在某个点之后不会再访问这些类型,并帮助捕获此后的类型访问相关的错误。
- GenericLoweringPhase:
- EarlyOptimizationPhase:
- 执行一些早期的优化,这些优化通常在图的结构变得更复杂之前进行。
- EffectControlLinearizationPhase:
- 线性化图中的效果和控制流,为后续的阶段提供一个更结构化的图。
- StoreStoreEliminationPhase(如果
FLAG_turbo_store_elimination 启用):
- ControlFlowOptimizationPhase(如果
FLAG_turbo_cf_optimization 启用):
- MemoryOptimizationPhase:
- 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 方法的功能:
- Phi 节点的作用:
Phi节点用于表示程序中控制流的合并点,如if-else或循环结构之后。在这些点上,一个变量可能有来自多个不同执行路径的不同值,因此它们可能具有不同的类型。
- Union 方法的功能:
Type::Union 方法用于创建一个新类型,它是其输入类型的并集。这意味着这个新类型可以接受任何一个输入类型的值。在 Phi 节点的上下文中,这是必要的,因为根据程序的不同执行路径,该变量可能具有不同的类型。
- 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
结果
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节点被删除
全局结果:CheckBounds节点没有被删除
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
编译运行
测试代码
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
对应的typer lowering优化
chatgpt相关猜测
在缺乏 duplicate-addition-reducer.cc 文件的具体实现细节的情况下,我可以根据常见的编译器优化技术,大致分析 DuplicateAdditionReducer 可能在 V8 JavaScript 引擎中的作用:
DuplicateAdditionReducer 可能的作用
-
消除冗余计算:
- 这个优化器可能被设计用来消除重复的加法操作。例如,如果同一个加法操作(如
a + b)在操作数 a 和 b 不变的情况下多次执行,优化器可能通过一次计算并重用结果来进行优化。
-
公共子表达式消除:
- 这是编译器中的一种标准优化技术。如果表达式
a + b 被计算了不止一次,且在这些计算之间 a 和 b 的值没有改变,编译器可以消除重复的计算,只保留一次计算。
-
重排序和合并操作:
- 优化器可能会重新排列加法操作以提高效率,尤其是如果能将它们合并成更简单的形式(比如将
a + a + a 转换为 3 * a)。
-
常量折叠和传播:
- 如果优化器识别出涉及常量的加法,它可以在编译时预先计算这些表达式。例如,将
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
TurboFan简单理解
环境
具体测试环境
遇到的问题
问题一:
编译完
chromium之后,启动chrome失败,提示初始化数据库失败解决方案: 删除
~/.config/chromium相关配置信息再次启动,成功初始化,并运行
问题二:
使用
ubuntu 18.04系统直接安装的nodejs和npm编译v8的tools/turbolizer失败解决方案:使用
v8官方github.io的工具,工具地址: https://v8.github.io/tools/head/turbolizer/index.htmlCTL+L上传对应的生成的json文件跟踪调试Turbofan
通过样例分析v8编译优化过程
turbolizer数据执行:
out/Release/d8 test.js --allow-natives-syntax --trace-turbo将生成的
json文件导入turbolizer查看具体结构查看输出信息
源码:
src/runtime-profiler\.ccturbofan执行流程GetOptimizedCode函数分析分析
分析
优化代码
根据
job的执行步骤,会有如下三步再经过分析
PrepareJob/ExecuteJobFinalizeJob有如下最终实现,并分别分析PrepareJobImpl分析,主要功能在于开始构图构图代码中,会一开始就调用
GraphBuilderPhase来构建一个基础的简单图,用于后期各种优化在构图过程中,主要用了以下几个
phase,主要是生成初始的基础图,为后续优化做准备graph representation)。JavaScript函数的节点图。FLAG_concurrent_compiler_frontend时运行):JSOperators。构图之后进行优化
在
PipelineImpl::OptimizeGraph(Linkage* linkage)函数中,V8使用了一系列的Phases来优化中间表示的图。以下是这个函数中的各个Phases及其对应的作用:LoopPeelingPhase: 对于常数循环条件,将循环的第一次迭代从循环中"剥离"出来。LoopExitEliminationPhase: 优化掉那些永远不会被执行的循环退出点。FLAG_turbo_load_elimination启用):FLAG_turbo_escape启用):FLAG_turbo_store_elimination启用):FLAG_turbo_cf_optimization启用):在每个阶段后面,都有一个
RunPrintAndVerify调用,用于在开发过程中验证和跟踪图的状态。最后,
ComputeScheduledGraph()负责计算调度的图,而SelectInstructions(linkage)则选择机器指令来完成编译过程。这个函数中的
Phases负责对图进行各种优化,使得最后生成的机器代码更加高效。重点phase分析
以上只是简单阐述了部分
phase,现在来详细分析一下跟本文有关的phaseTyperPhase
其定义
我们通过设置断点和日志来看一下
ReduceNode最终调用了哪些Reducer查看一下其中的输出
根据分析代码可以发现,不同的
Reducer会调用其对应的Reduce来处理各种节点,其中Reducer为Typer时,调用的是Visitor,调用栈如下具体分析
Visitor的Reduce根据上面的代码可以发现,当
opcode为NumberConstant时,会调用TypeNumberConstant,在该函数中会调用NewConstant用来构造一个整数,并且是一个范围通过调用测试代码,会在
v8的测试工具中看到Constant确实是有Range的,注意调用时记得使用--trace-opt文章中提到了
SSA相关的内容,以前接触的少,所以搜了一些资料根据其中的测试代码
测试
~/v8/v8/out/Release/d8 --allow-natives-syntax --trace-opt --trace-turbo test_typephi.js结果
SimplifiedLoweringPhase
查看
SimplifiedLoweringPhaseRun<SimplifiedLoweringPhase>(); RunPrintAndVerify(SimplifiedLoweringPhase::phase_name(), true);在该
Phase中,我们只关注其中CheckBounds节点的优化问题CheckBounds分析测试代码1
测试代码2
在
v8源码simplified-lowering.cc中的CheckBounds执行结果
局部结果:
CheckBounds节点被删除全局结果:
CheckBounds节点没有被删除google ctf 2018 final just in time
看了上面的
TurboFan相关的内容,现在来看一个google的ctf来加深对上面内容的理解看一下
v8相关的patch应用patch
编译运行
测试代码
优化前type lowering
对应的typer lowering优化
chatgpt相关猜测
在缺乏
duplicate-addition-reducer.cc文件的具体实现细节的情况下,我可以根据常见的编译器优化技术,大致分析DuplicateAdditionReducer可能在 V8 JavaScript 引擎中的作用:DuplicateAdditionReducer可能的作用消除冗余计算:
a + b)在操作数a和b不变的情况下多次执行,优化器可能通过一次计算并重用结果来进行优化。公共子表达式消除:
a + b被计算了不止一次,且在这些计算之间a和b的值没有改变,编译器可以消除重复的计算,只保留一次计算。重排序和合并操作:
a + a + a转换为3 * a)。常量折叠和传播:
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