
一 簡介
本文是一個 V8 編譯原理知識的介紹文章,旨在讓大家感性的瞭解 JavaScript 在 V8 中的解析過程。本文主要的撰寫流程如下:
-
直譯器和編譯器:計算機編譯原理的基礎知識介紹
-
V8 的編譯原理:基於計算機編譯原理的知識,瞭解 V8 對於 JavaScript 的解析流程
-
V8 的執行時表現:結合 V8 的編譯原理,實踐 V8 在解析流程中的具體執行表現
本文僅代表個人觀點,文中若有錯誤歡迎指正。
二 直譯器和編譯器
大家可能一直疑惑的問題:JavaScript 是一門解釋型語言嗎?要了解這個問題,首先需要初步瞭解什麼是直譯器和編譯器以及它們的特點是什麼。
1 直譯器
直譯器的作用是將某種語言編寫的源程式作為輸入,將該源程式執行的結果作為輸出,例如 Perl、Scheme、APL 等都是使用直譯器進行轉換執行:

2 編譯器
-
如何分析不同高階程式語言設計的源程式
-
如何將源程式的功能等價對映到不同指令系統的目標機器

中間表示(IR)
中間表示(Intermediate Representation,IR)是程式結構的一種表現方式,它會比抽象語法樹(Abstract Syntax Tree,AST)更加接近組合語言或者指令集,同時也會保留源程式中的一些高階資訊,具體作用包括:
-
易於編譯器的錯誤除錯,容易識別是 IR 之前的前端還是之後的後端出的問題
-
可以使得編譯器的職責更加分離,源程式的編譯更多關注如何轉換成 IR,而不是去適配不同的指令集
-
IR 更加接近指令集,從而相對於原始碼可以更加節省記憶體空間

最佳化編譯器
IR 本身可以做到多趟迭代從而最佳化源程式,在每一趟迭代的過程中可以研究程式碼並記錄最佳化的細節,方便後續的迭代查詢並利用這些最佳化資訊,最終可以高效輸出更優的目標程式:

最佳化器可以對 IR 進行一趟或者多趟處理,從而生成更快執行速度或者更小體積的目標程式(例如找到迴圈中不變的計算並對其進行最佳化從而減少運算次數),也可能用於產生更少異常或者更低功耗的目標程式。除此之外,前端和後端內部還可以細分為多個處理步驟,具體如下圖所示:

3 兩者的特性比較

4 JIT 編譯技術

5 混合動態編譯技術
為了解決 JavaScript 在執行時效能較慢的問題,可以透過引入 JIT 技術,並採用混合動態編譯的方式來提升 JavaScript 的執行效能,具體思路如下所示:

採用上述編譯框架後,可以使得 JavaScript 語言:
-
啟動速度快:在 JavaScript 啟動的時候採用解釋執行的方式執行,利用瞭解釋器啟動速度快的特性
-
執行效能高:在 JavaScript 執行的過程中可以對程式碼進行監控,從而使用 JIT 技術對程式碼進行編譯最佳化
三 V8 的編譯原理

1 Ignition 直譯器

這裡不會過多講解每個執行流程的細節問題。
2 TurboFan 最佳化編譯器

圖片出處 An Introduction to Speculative Optimization in V8。
這裡的去最佳化是指讓程式碼回退到 Ignition 進行解釋執行,去最佳化本質是因為機器碼已經不能滿足執行訴求,例如一個變數從 string 型別轉變成 number 型別,機器碼編譯的是 string 型別,此時已經無法再滿足執行訴求,因此 V8 會執行去最佳化動作,將程式碼回退到 Ignition 進行解釋執行。
四 V8 的執行時表現
1 D8 除錯工具
如果想了解 JavaScript 在 V8 中的編譯時和執行時資訊,可以使用除錯工具 D8。D8 是 V8 引擎的命令列 Shell,可以檢視 AST 生成、中間程式碼 ByteCode、最佳化程式碼、反最佳化程式碼、最佳化編譯器的統計資料、程式碼的 GC 等資訊。D8 的安裝方式有很多,如下所示:
-
方法一:根據 V8 官方文件 Using d8 以及 Building V8 with GN 進行工具鏈的下載和編譯
-
方法二:使用別人已經編譯好的 D8 工具,可能版本會有滯後性,例如 Mac 版
-
方法三:使用 JavaScript 引擎版本管理工具,例如 jsvu,可以下載到最新編譯好的 JavaScript 引擎
本文使用方法三安裝 v8-debug 工具,安裝完成後執行 v8-debug –help 可以檢視有哪些命令:
# 執行 help 命令檢視支援的引數
v8-debug --help
Synopsis:
shell [options] [--shell] [<file>...]
d8 [options] [-e <string>] [--shell] [[--module|--web-snapshot] <file>...]
-e execute a string in V8
--shell run an interactive JavaScript shell
--module execute a file as a JavaScript module
--web-snapshot execute a file as a web snapshot
SSE3=1 SSSE3=1 SSE4_1=1 SSE4_2=1 SAHF=1 AVX=1 AVX2=1 FMA3=1 BMI1=1 BMI2=1 LZCNT=1 POPCNT=1 ATOM=0
The following syntax for options is accepted (both '-' and '--' are ok):
--flag (bool flags only)
--no-flag (bool flags only)
--flag=value (non-bool flags only, no spaces around '=')
--flag value (non-bool flags only)
-- (captures all remaining args in JavaScript)
Options:
# 列印生成的位元組碼
--print-bytecode (print bytecode generated by ignition interpreter)
type: booldefault: --noprint-bytecode
# 跟蹤被最佳化的資訊
--trace-opt (trace optimized compilation)
type: booldefault: --notrace-opt
--trace-opt-verbose (extra verbose optimized compilation tracing)
type: booldefault: --notrace-opt-verbose
--trace-opt-stats (trace optimized compilation statistics)
type: booldefault: --notrace-opt-stats
# 跟蹤去最佳化的資訊
--trace-deopt (trace deoptimization)
type: booldefault: --notrace-deopt
--log-deopt (log deoptimization)
type: booldefault: --nolog-deopt
--trace-deopt-verbose (extra verbose deoptimization tracing)
type: booldefault: --notrace-deopt-verbose
--print-deopt-stress (print number of possible deopt points)
# 檢視編譯生成的 AST
--print-ast (print source AST)
type: booldefault: --noprint-ast
# 檢視編譯生成的程式碼
--print-code (print generated code)
type: booldefault: --noprint-code
# 檢視最佳化後的程式碼
--print-opt-code (print optimized code)
type: booldefault: --noprint-opt-code
# 允許在原始碼中使用 V8 提供的原生 API 語法
--allow-natives-syntax (allow natives syntax)
type: booldefault: --noallow-natives-syntax
2 生成 AST
functionadd(x, y) {
return x + y
}
console.log(add(1, 2));
v8-debug --print-ast ./index.js
[generating bytecode for function: ]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . FUNCTION "add" = function add
. EXPRESSION STATEMENT at 41
. . ASSIGN at -1
. . . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
. . . CALL
. . . . PROPERTY at49
. . . . . VAR PROXY unallocated (0x7fb8c080e6f0) (mode = DYNAMIC_GLOBAL, assigned = false) "console"
. . . . . NAMElog
. . . . CALL
. . . . . VAR PROXY unallocated (0x7fb8c080e470) (mode = VAR, assigned = true) "add"
. . . . . LITERAL 1
. . . . . LITERAL 2
. RETURNat-1
. . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
[generating bytecode forfunction: add]
--- AST ---
FUNC at12
. KIND 0
. LITERAL ID1
. SUSPENDCOUNT0
. NAME"add"
. PARAMS
. . VAR (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VAR (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. DECLS
. . VARIABLE (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VARIABLE (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. RETURNat25
. . ADDat34
. . . VAR PROXY parameter[0] (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . . VAR PROXY parameter[1] (0x7fb8c080e580) (mode = VAR, assigned = false) "y"

3 生成位元組碼
AST 會經過 Ignition 直譯器的 BytecodeGenerator 函式生成位元組碼(中間表示),我們可以透過 –print-bytecode 引數來列印位元組碼資訊:
--print-bytecode ./index.js
bytecode for function: (0x3ab2082933f5 <SharedFunctionInfo>)]
Bytecodelength: 43
Parametercount 1
Registercount 6
Framesize 48
OSRnesting level: 0
BytecodeAge: 0
0x3ab2082934be@ 0 : 13 00 LdaConstant [0]
0x3ab2082934c0@ 2 : c3 Star1
0x3ab2082934c1@ 3 : 19 fe f8 Mov <closure>, r2
0x3ab2082934c4@ 6 : 65 52 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
0x3ab2082934c9@ 11 : 21 01 00 LdaGlobal [1], [0]
0x3ab2082934cc@ 14 : c2 Star2
0x3ab2082934cd@ 15 : 2d f8 02 02 LdaNamedProperty r2, [2], [2]
0x3ab2082934d1@ 19 : c3 Star1
0x3ab2082934d2@ 20 : 21 03 04 LdaGlobal [3], [4]
0x3ab2082934d5@ 23 : c1 Star3
0x3ab2082934d6@ 24 : 0d 01 LdaSmi [1]
0x3ab2082934d8@ 26 : c0 Star4
0x3ab2082934d9@ 27 : 0d 02 LdaSmi [2]
0x3ab2082934db@ 29 : bf Star5
0x3ab2082934dc@ 30 : 63 f7 f6 f5 06 CallUndefinedReceiver2 r3, r4, r5, [6]
0x3ab2082934e1@ 35 : c1 Star3
0x3ab2082934e2@ 36 : 5e f9 f8 f7 08 CallProperty1 r1, r2, r3, [8]
0x3ab2082934e7@ 41 : c4 Star0
0x3ab2082934e8@ 42 : a9 Return
Constantpool (size = 4)
0x3ab208293485: [FixedArray] in OldSpace
map: 0x3ab208002205 <Map>
length: 4
0: 0x3ab20829343d <FixedArray[2]>
1: 0x3ab208202741 <String[7]: #console>
2: 0x3ab20820278d <String[3]: #log>
3: 0x3ab208003f09 <String[3]: #add>
HandlerTable (size = 0)
SourcePosition Table (size = 0)
bytecode for function: add (0x3ab20829344d <SharedFunctionInfo add>)]
Bytecodelength: 6
接受 3 個引數, 1 個隱式的 this,以及顯式的 x 和 y
Parametercount 3
Registercount 0
不需要區域性變數,因此幀大小為 0
Framesize 0
OSRnesting level: 0
BytecodeAge: 0
0x3ab2082935f6@ 0 : 0b 04 Ldar a1
0x3ab2082935f8@ 2 : 39 03 00 Add a0, [0]
0x3ab2082935fb@ 5 : a9 Return
Constantpool (size = 0)
HandlerTable (size = 0)
SourcePosition Table (size = 0)
// Load Accumulator Register
// 載入暫存器 a1 的值到累加器中
Ldar a1
// 讀取暫存器 a0 的值並累加到累加器中,相加之後的結果會繼續放在累加器中
// [0] 指向 Feedback Vector Slot,Ignition 會收集值的分析資訊,為後續的 TurboFan 最佳化做準備
Add a0, [0]
// 轉交控制權給呼叫者,並返回累加器中的值
Return
關於更多位元組碼的資訊可檢視 Understanding V8’s Bytecode。
4 最佳化和去最佳化
function add(x, y) {
// + 運算子是 JavaScript 中非常複雜的一個操作
return x + y
}
add(1, 2);
add('1', 2);
add(null, 2);
add(undefined, 2);
add([], 2);
add({}, 2);
add([], {});

為了檢視 add 函式的執行時反饋資訊,我們可以透過 V8 提供的 Native API 來列印 add 函式的執行時資訊,具體如下所示:
function add(x, y) {
return x + y
}
// 注意這裡預設採用了 ClosureFeedbackCellArray,為了檢視效果,強制開啟 FeedbackVector
// 更多資訊檢視: A lighter V8:https://v8.dev/blog/v8-lite
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
// 列印 add 詳細的執行時資訊
%DebugPrint(add);
v8-debug --allow-natives-syntax ./index.js
DebugPrint: 0x1d22082935b9: [Function] in OldSpace
- map: 0x1d22082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x1d2208283b79 <JSFunction (sfi = 0x1d220820abbd)>
- elements: 0x1d220800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x1d2208293491 <SharedFunctionInfo add>
- name: 0x1d2208003f09 <String[3]: #add>
// 包含 Ignition 直譯器的 trampoline 指標
- builtin: InterpreterEntryTrampoline
- formal_parameter_count: 2
- kind: NormalFunction
- context: 0x1d2208283649 <NativeContext[263]>
- code: 0x1d2200005181 <Code BUILTIN InterpreterEntryTrampoline>
- interpreted
- bytecode: 0x1d2208293649 <BytecodeArray[6]>
- source code: (x, y) {
return x + y
}
- properties: 0x1d220800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x1d2208004bb5: [String] in ReadOnlySpace: #length: 0x1d2208204431 <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d2208004dfd: [String] in ReadOnlySpace: #name: 0x1d22082043ed <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d2208003fad: [String] in ReadOnlySpace: #arguments: 0x1d2208204365 <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d22080041f1: [String] in ReadOnlySpace: #caller: 0x1d22082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor
0x1d22080050b1: [String] in ReadOnlySpace: #prototype: 0x1d2208204475 <AccessorInfo> (const accessor descriptor), location: descriptor
}
// 以下是詳細的反饋資訊
- feedback vector: 0x1d2208293691: [FeedbackVector] in OldSpace
- map: 0x1d2208002711 <Map>
- length: 1
- shared function info: 0x1d2208293491 <SharedFunctionInfo add>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
- invocation count: 0
- profiler ticks: 0
- closure feedback cell array: 0x1d22080032b5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x1d2208002955 <Map>
- length: 0
- slot #0 BinaryOp BinaryOp:None {
[0]: 0
}
0x1d22082c2281: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x1d22080023b5 <undefined>
- prototype_validity cell: 0x1d22082044fd <Cell value= 1>
- instance descriptors (own) #5: 0x1d2208283c29 <DescriptorArray[5]>
- prototype: 0x1d2208283b79 <JSFunction (sfi = 0x1d220820abbd)>
- constructor: 0x1d2208283bf5 <JSFunction Function (sfi = 0x1d220820acb9)>
- dependent code: 0x1d22080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
這裡的 SharedFunctionInfo(SFI)中保留了一個 InterpreterEntryTrampoline 指標資訊,每個函式都會有一個指向 Ignition 直譯器的 trampoline 指標,每當 V8 需要進去去最佳化時,就會使用此指標使程式碼回退到直譯器相應的函式執行位置。
function add(x, y) {
return x + y
}
add(1, 2);
// 強制開啟函式最佳化
%OptimizeFunctionOnNextCall(add);
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
// 列印 add 詳細的執行時資訊
%DebugPrint(add);
v8-debug --allow-natives-syntax --trace-opt ./index.js
[manually marking 0x3872082935bd <JSFunction add (sfi = 0x3872082934b9)> for non-concurrent optimization]
// 這裡使用 TurboFan 最佳化編譯器對 add 函式進行編譯最佳化
[compiling method 0x3872082935bd <JSFunction add (sfi = 0x3872082934b9)> (target TURBOFAN) using TurboFan]
[optimizing 0x3872082935bd <JSFunction add (sfi = 0x3872082934b9)> (target TURBOFAN) - took 0.097, 2.003, 0.273 ms]
DebugPrint: 0x3872082935bd: [Function] in OldSpace
- map: 0x3872082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x387208283b79 <JSFunction (sfi = 0x38720820abbd)>
- elements: 0x38720800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- functionprototype:
- initial_map:
- shared_info: 0x3872082934b9 <SharedFunctionInfoadd>
- name: 0x387208003f09 <String[3]: #add>
- formal_parameter_count: 2
- kind: NormalFunction
- context: 0x387208283649 <NativeContext[263]>
- code: 0x387200044001 <CodeTURBOFAN>
- sourcecode: (x, y) {
return x + y
}
- properties: 0x38720800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x387208004bb5: [String] in ReadOnlySpace: #length: 0x387208204431 <AccessorInfo> (const accessor descriptor), location: descriptor
0x387208004dfd: [String] in ReadOnlySpace: #name: 0x3872082043ed <AccessorInfo> (const accessor descriptor), location: descriptor
0x387208003fad: [String] in ReadOnlySpace: #arguments: 0x387208204365 <AccessorInfo> (const accessor descriptor), location: descriptor
0x3872080041f1: [String] in ReadOnlySpace: #caller: 0x3872082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor
0x3872080050b1: [String] in ReadOnlySpace: #prototype: 0x387208204475 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x387208293685: [FeedbackVector] in OldSpace
- map: 0x387208002711 <Map>
- length: 1
- shared functioninfo: 0x3872082934b9 <SharedFunctionInfoadd>
- nooptimizedcode
- optimizationmarker: OptimizationMarker::kNone
- optimizationtier: OptimizationTier::kNone
// 呼叫次數增加了 1 次
- invocationcount: 1
- profilerticks: 0
- closurefeedbackcellarray: 0x3872080032b5: [ClosureFeedbackCellArray] inReadOnlySpace
- map: 0x387208002955 <Map>
- length: 0
- slot #0 BinaryOpBinaryOp:SignedSmall{
[0]: 1
}
0x3872082c2281: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x3872080023b5 <undefined>
- prototype_validity cell: 0x3872082044fd <Cell value= 1>
- instance descriptors (own) #5: 0x387208283c29 <DescriptorArray[5]>
- prototype: 0x387208283b79 <JSFunction (sfi = 0x38720820abbd)>
- constructor: 0x387208283bf5 <JSFunction Function (sfi = 0x38720820acb9)>
- dependent code: 0x3872080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
function add(x, y) {
return x + y
}
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
%OptimizeFunctionOnNextCall(add);
add(1, 2);
// 改變 add 函式的傳入引數型別,之前都是 number 型別,這裡傳入 string 型別
add(1, '2');
%DebugPrint(add);
ziyi@B-D0UTG8WN-2029 .jsvu % v8-debug --allow-natives-syntax --trace-deopt ./index.js
// 執行去最佳化,reason: not a Smi(Smi 在後續的系列文章中進行講解,這裡說明傳入的不是一個小整數型別)
[bailout (kind: deopt-eager, reason: not a Smi: begin. deoptimizing 0x08f70829363d <JSFunction add (sfi = 0x8f7082934c9)>, opt id0, node id58, bytecode offset2, deopt exit1, FP to SP delta 32, caller SP 0x7ffee9ce7d70, pc 0x08f700044162]
DebugPrint: 0x8f70829363d: [Function] in OldSpace
- map: 0x08f7082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x08f708283b79 <JSFunction (sfi = 0x8f70820abbd)>
- elements: 0x08f70800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x08f7082934c9 <SharedFunctionInfo add>
- name: 0x08f708003f09 <String[3]: #add>
- formal_parameter_count: 2
- kind: NormalFunction
- context: 0x08f708283649 <NativeContext[263]>
- code: 0x08f700044001 <Code TURBOFAN>
- interpreted
- bytecode: 0x08f7082936cd <BytecodeArray[6]>
- source code: (x, y) {
return x + y
}
- properties: 0x08f70800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x8f708004bb5: [String] in ReadOnlySpace: #length: 0x08f708204431 <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f708004dfd: [String] in ReadOnlySpace: #name: 0x08f7082043ed <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f708003fad: [String] in ReadOnlySpace: #arguments: 0x08f708204365 <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f7080041f1: [String] in ReadOnlySpace: #caller: 0x08f7082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor
0x8f7080050b1: [String] in ReadOnlySpace: #prototype: 0x08f708204475 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: 0x8f708293715: [FeedbackVector] in OldSpace
- map: 0x08f708002711 <Map>
- length: 1
- sharedfunction info: 0x08f7082934c9 <SharedFunctionInfo add>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
- invocation count: 1
- profiler ticks: 0
- closure feedback cell array: 0x8f7080032b5: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x08f708002955 <Map>
- length: 0
- slot #0 BinaryOp BinaryOp:Any {
[0]: 127
}
0x8f7082c2281: [Map]
- type: JS_FUNCTION_TYPE
- instancesize: 32
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- constructor
- has_prototype_slot
- back pointer: 0x08f7080023b5 <undefined>
- prototype_validity cell: 0x08f7082044fd <Cell value= 1>
- instance descriptors (own) #5: 0x08f708283c29 <DescriptorArray[5]>
- prototype: 0x08f708283b79 <JSFunction (sfi = 0x8f70820abbd)>
- constructor: 0x08f708283bf5 <JSFunction Function (sfi = 0x8f70820acb9)>
- dependent code: 0x08f7080021b9 <Other heapobject (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
五 總結
E-MapReduce入門訓練營
關鍵詞
型別
程式碼
引數
位元組碼
編譯器