
一 简介
解释器和编译器:计算机编译原理的基础知识介绍
V8 的编译原理:基于计算机编译原理的知识,了解 V8 对于 JavaScript 的解析流程
V8 的运行时表现:结合 V8 的编译原理,实践 V8 在解析流程中的具体运行表现
本文仅代表个人观点,文中若有错误欢迎指正。
二 解释器和编译器
1 解释器

2 编译器
如何分析不同高级程序语言设计的源程序
如何将源程序的功能等价映射到不同指令系统的目标机器

中间表示(IR)
易于编译器的错误调试,容易识别是 IR 之前的前端还是之后的后端出的问题
可以使得编译器的职责更加分离,源程序的编译更多关注如何转换成 IR,而不是去适配不同的指令集
IR 更加接近指令集,从而相对于源码可以更加节省内存空间

优化编译器


3 两者的特性比较

4 JIT 编译技术

5 混合动态编译技术

启动速度快:在 JavaScript 启动的时候采用解释执行的方式运行,利用了解释器启动速度快的特性
运行性能高:在 JavaScript 运行的过程中可以对代码进行监控,从而使用 JIT 技术对代码进行编译优化
三 V8 的编译原理

1 Ignition 解释器

这里不会过多讲解每个执行流程的细节问题。
2 TurboFan 优化编译器

图片出处 An Introduction to Speculative Optimization in V8。
这里的去优化是指让代码回退到 Ignition 进行解释执行,去优化本质是因为机器码已经不能满足运行诉求,例如一个变量从 string 类型转变成 number 类型,机器码编译的是 string 类型,此时已经无法再满足运行诉求,因此 V8 会执行去优化动作,将代码回退到 Ignition 进行解释执行。
四 V8 的运行时表现
1 D8 调试工具
方法一:根据 V8 官方文档 Using d8 以及 Building V8 with GN 进行工具链的下载和编译
方法二:使用别人已经编译好的 D8 工具,可能版本会有滞后性,例如 Mac 版
方法三:使用 JavaScript 引擎版本管理工具,例如 jsvu,可以下载到最新编译好的 JavaScript 引擎
# 执行 help 命令查看支持的参数v8-debug --helpSynopsis: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 snapshotSSE3=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=0The 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: bool default: --noprint-bytecode# 跟踪被优化的信息--trace-opt (trace optimized compilation)type: bool default: --notrace-opt--trace-opt-verbose (extra verbose optimized compilation tracing)type: bool default: --notrace-opt-verbose--trace-opt-stats (trace optimized compilation statistics)type: bool default: --notrace-opt-stats# 跟踪去优化的信息--trace-deopt (trace deoptimization)type: bool default: --notrace-deopt--log-deopt (log deoptimization)type: bool default: --nolog-deopt--trace-deopt-verbose (extra verbose deoptimization tracing)type: bool default: --notrace-deopt-verbose--print-deopt-stress (print number of possible deopt points)# 查看编译生成的 AST--print-ast (print source AST)type: bool default: --noprint-ast# 查看编译生成的代码--print-code (print generated code)type: bool default: --noprint-code# 查看优化后的代码--print-opt-code (print optimized code)type: bool default: --noprint-opt-code# 允许在源代码中使用 V8 提供的原生 API 语法--allow-natives-syntax (allow natives syntax)type: bool default: --noallow-natives-syntax
2 生成 AST
function add(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 at 49. . . . . VAR PROXY unallocated (0x7fb8c080e6f0) (mode = DYNAMIC_GLOBAL, assigned = false) "console". . . . . NAME log. . . . CALL. . . . . VAR PROXY unallocated (0x7fb8c080e470) (mode = VAR, assigned = true) "add". . . . . LITERAL 1. . . . . LITERAL 2. RETURN at -1. . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"[generating bytecode for function: add]--- AST ---FUNC at 12. KIND 0. LITERAL ID 1. SUSPEND COUNT 0. 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". RETURN at 25. . ADD at 34. . . VAR PROXY parameter[0] (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x". . . VAR PROXY parameter[1] (0x7fb8c080e580) (mode = VAR, assigned = false) "y"

3 生成字节码
v8-debug --print-bytecode ./index.js[generated bytecode for function: (0x3ab2082933f5 <SharedFunctionInfo>)]Bytecode length: 43Parameter count 1Register count 6Frame size 48OSR nesting level: 0Bytecode Age: 00x3ab2082934be @ 0 : 13 00 LdaConstant [0]0x3ab2082934c0 @ 2 : c3 Star10x3ab2082934c1 @ 3 : 19 fe f8 Mov <closure>, r20x3ab2082934c4 @ 6 : 65 52 01 f9 02 CallRuntime [DeclareGlobals], r1-r20x3ab2082934c9 @ 11 : 21 01 00 LdaGlobal [1], [0]0x3ab2082934cc @ 14 : c2 Star20x3ab2082934cd @ 15 : 2d f8 02 02 LdaNamedProperty r2, [2], [2]0x3ab2082934d1 @ 19 : c3 Star10x3ab2082934d2 @ 20 : 21 03 04 LdaGlobal [3], [4]0x3ab2082934d5 @ 23 : c1 Star30x3ab2082934d6 @ 24 : 0d 01 LdaSmi [1]0x3ab2082934d8 @ 26 : c0 Star40x3ab2082934d9 @ 27 : 0d 02 LdaSmi [2]0x3ab2082934db @ 29 : bf Star50x3ab2082934dc @ 30 : 63 f7 f6 f5 06 CallUndefinedReceiver2 r3, r4, r5, [6]0x3ab2082934e1 @ 35 : c1 Star30x3ab2082934e2 @ 36 : 5e f9 f8 f7 08 CallProperty1 r1, r2, r3, [8]0x3ab2082934e7 @ 41 : c4 Star00x3ab2082934e8 @ 42 : a9 ReturnConstant pool (size = 4)0x3ab208293485: [FixedArray] in OldSpace- map: 0x3ab208002205 <Map>- length: 40: 0x3ab20829343d <FixedArray[2]>1: 0x3ab208202741 <String[7]: #console>2: 0x3ab20820278d <String[3]: #log>3: 0x3ab208003f09 <String[3]: #add>Handler Table (size = 0)Source Position Table (size = 0)[generated bytecode for function: add (0x3ab20829344d <SharedFunctionInfo add>)]Bytecode length: 6// 接受 3 个参数, 1 个隐式的 this,以及显式的 x 和 yParameter count 3Register count 0// 不需要局部变量,因此帧大小为 0Frame size 0OSR nesting level: 0Bytecode Age: 00x3ab2082935f6 @ 0 : 0b 04 Ldar a10x3ab2082935f8 @ 2 : 39 03 00 Add a0, [0]0x3ab2082935fb @ 5 : a9 ReturnConstant pool (size = 0)Handler Table (size = 0)Source Position 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([], {});

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.jsDebugPrint: 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: descriptor0x1d2208004dfd: [String] in ReadOnlySpace: #name: 0x1d22082043ed <AccessorInfo> (const accessor descriptor), location: descriptor0x1d2208003fad: [String] in ReadOnlySpace: #arguments: 0x1d2208204365 <AccessorInfo> (const accessor descriptor), location: descriptor0x1d22080041f1: [String] in ReadOnlySpace: #caller: 0x1d22082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor0x1d22080050b1: [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]- function prototype:- initial_map:- shared_info: 0x3872082934b9 <SharedFunctionInfo add>- name: 0x387208003f09 <String[3]: #add>- formal_parameter_count: 2- kind: NormalFunction- context: 0x387208283649 <NativeContext[263]>- code: 0x387200044001 <Code TURBOFAN>- source code: (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: descriptor0x387208004dfd: [String] in ReadOnlySpace: #name: 0x3872082043ed <AccessorInfo> (const accessor descriptor), location: descriptor0x387208003fad: [String] in ReadOnlySpace: #arguments: 0x387208204365 <AccessorInfo> (const accessor descriptor), location: descriptor0x3872080041f1: [String] in ReadOnlySpace: #caller: 0x3872082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor0x3872080050b1: [String] in ReadOnlySpace: #prototype: 0x387208204475 <AccessorInfo> (const accessor descriptor), location: descriptor}- feedback vector: 0x387208293685: [FeedbackVector] in OldSpace- map: 0x387208002711 <Map>- length: 1- shared function info: 0x3872082934b9 <SharedFunctionInfo add>- no optimized code- optimization marker: OptimizationMarker::kNone- optimization tier: OptimizationTier::kNone// 调用次数增加了 1 次- invocation count: 1- profiler ticks: 0- closure feedback cell array: 0x3872080032b5: [ClosureFeedbackCellArray] in ReadOnlySpace- map: 0x387208002955 <Map>- length: 0- slot #0 BinaryOp BinaryOp: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 id 0, node id 58, bytecode offset 2, deopt exit 1, 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: descriptor0x8f708004dfd: [String] in ReadOnlySpace: #name: 0x08f7082043ed <AccessorInfo> (const accessor descriptor), location: descriptor0x8f708003fad: [String] in ReadOnlySpace: #arguments: 0x08f708204365 <AccessorInfo> (const accessor descriptor), location: descriptor0x8f7080041f1: [String] in ReadOnlySpace: #caller: 0x08f7082043a9 <AccessorInfo> (const accessor descriptor), location: descriptor0x8f7080050b1: [String] in ReadOnlySpace: #prototype: 0x08f708204475 <AccessorInfo> (const accessor descriptor), location: descriptor}- feedback vector: 0x8f708293715: [FeedbackVector] in OldSpace- map: 0x08f708002711 <Map>- length: 1- shared function 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- 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: 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 heap object (WEAK_FIXED_ARRAY_TYPE)>- construction counter: 0
五 总结
E-MapReduce入门训练营
文章转载自阿里技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




