共计 9499 个字符,预计需要花费 24 分钟才能阅读完成。
1. 摘要
本次是第六篇,解说 V8 中形象语法树 (abstract syntax code,AST) 到字节码 (bytecode) 的翻译过程。AST 是源代码的形象语法结构的树状示意,是语法分析的输入后果,bytecode 是一种体系结构无关的、在 V8 中能够运行的形象机器码,不依赖指令集。本文中,咱们以 AST 作为 V8 输出,从 AST 生成后开始调试(Debug),解说 bytecode 生成过程,剖析外围源码和重要数据结构,如图 1 所示。本文内容的组织形式:介绍字节码,解说字节码原理,如何看懂字节码(章节 2);AST 到 bytecode 的翻译过程、源码剖析(章节 3)。
2. 字节码介绍
字节码是机器码的形象示意,采纳和物理 CPU 雷同的计算模型进行设计。字节码是最小性能齐备集,JavaScript 源码的任何性能都能够等价转换成字节码的组合。V8 有数以百计的字节码,例如 Add
和Sub
等简略操作,还有 LdaNamedProperty
等属性加载操作。每个字节码都能够指定寄存器作为其操作数,生成字节码的过程中应用寄存器 r0,r1,r2,… 和累加寄存器(accumulator register)。累加器是和其它寄存器一样的惯例寄存器,但不同的是累加器的操作没有显式给出指令,具体来说,Add r1
将寄存器 r1
中的值和累加器中的值进行加法运算,在这个过程不须要显示指出累加器。字节码的定义在 v8/src/interpreter/bytecodes.h 中,上面展现一部分相干源码。
#define BYTECODE_LIST_WITH_UNIQUE_HANDLERS(V) \
/* Extended width operands */ \
V(Wide, ImplicitRegisterUse::kNone) \
V(ExtraWide, ImplicitRegisterUse::kNone) \
\
/* Debug Breakpoints - one for each possible size of unscaled bytecodes */ \
/* and one for each operand widening prefix bytecode */ \
V(DebugBreakWide, ImplicitRegisterUse::kReadWriteAccumulator) \
V(DebugBreakExtraWide, ImplicitRegisterUse::kReadWriteAccumulator) \
V(DebugBreak0, ImplicitRegisterUse::kReadWriteAccumulator) \
V(DebugBreak1, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg) \
V(DebugBreak2, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg, OperandType::kReg) \
V(DebugBreak3, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg, OperandType::kReg, OperandType::kReg) \
V(DebugBreak4, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg, OperandType::kReg, OperandType::kReg, \
OperandType::kReg) \
V(DebugBreak5, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kRuntimeId, OperandType::kReg, OperandType::kReg) \
V(DebugBreak6, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kRuntimeId, OperandType::kReg, OperandType::kReg, \
OperandType::kReg) \
\
/* Side-effect-free bytecodes -- carefully ordered for efficient checks */ \
/* - [Loading the accumulator] */ \
V(Ldar, ImplicitRegisterUse::kWriteAccumulator, OperandType::kReg) \
V(LdaZero, ImplicitRegisterUse::kWriteAccumulator) \
V(LdaSmi, ImplicitRegisterUse::kWriteAccumulator, OperandType::kImm) \
V(LdaUndefined, ImplicitRegisterUse::kWriteAccumulator) \
V(LdaNull, ImplicitRegisterUse::kWriteAccumulator) \
V(LdaTheHole, ImplicitRegisterUse::kWriteAccumulator) \
V(LdaTrue, ImplicitRegisterUse::kWriteAccumulator) \
V(LdaFalse, ImplicitRegisterUse::kWriteAccumulator) \
V(LdaConstant, ImplicitRegisterUse::kWriteAccumulator, OperandType::kIdx) \
V(LdaContextSlot, ImplicitRegisterUse::kWriteAccumulator, OperandType::kReg, \
OperandType::kIdx, OperandType::kUImm) \
V(LdaImmutableContextSlot, ImplicitRegisterUse::kWriteAccumulator, \
OperandType::kReg, OperandType::kIdx, OperandType::kUImm) \
V(LdaCurrentContextSlot, ImplicitRegisterUse::kWriteAccumulator, \
OperandType::kIdx) \
V(LdaImmutableCurrentContextSlot, ImplicitRegisterUse::kWriteAccumulator, \
OperandType::kIdx) \
/* - [Register Loads] */ \
V(Star, ImplicitRegisterUse::kReadAccumulator, OperandType::kRegOut) \
V(Mov, ImplicitRegisterUse::kNone, OperandType::kReg, OperandType::kRegOut) \
V(PushContext, ImplicitRegisterUse::kReadAccumulator, OperandType::kRegOut) \
V(PopContext, ImplicitRegisterUse::kNone, OperandType::kReg) \
/* - [Test Operations] */ \
V(TestReferenceEqual, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg) \
V(TestUndetectable, ImplicitRegisterUse::kReadWriteAccumulator) \
V(TestNull, ImplicitRegisterUse::kReadWriteAccumulator) \
V(TestUndefined, ImplicitRegisterUse::kReadWriteAccumulator) \
V(TestTypeOf, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kFlag8) \
//......... 省略很多.....
下面这段代码是字节码的宏定义,用语句 V(Ldar, ImplicitRegisterUse::kWriteAccumulator, OperandType::kReg)
举例说明,Ldar
是加载数据到累加器,ImplicitRegisterUse::kWriteAccumulator, OperandType::kReg
阐明了 Ldar
指令的源操作数和目标操作数,具体讲两条字节码的含意,如下:
(1) LdaSmi [1],这里的 [1] 是 Smi 小整型 (small int) 常量,加载到累加器中,如图 2 所示。
(2) Star r1,这里的 r1 是 r1 寄存器,把累加器中的值写入到 r1 寄存器,目前累加器的值为 1,执行完后 r1 的值为 1,如图 3 所示。
其它字节码指令参见 V8 的指令定义文件,这里不再赘述。V8 为了晋升性能,会把屡次执行的字节码标记为热点代码,应用优化编译器 (TurboFan) 把热点代码翻译成机器相干的本地指令,达到进步运行效率的目标,如图 4 所示。
解释器将 AST 翻译成字节码比 TurboFan 用时更短,对于运行次数较少的代码十分适合,即不在运行次数较少的代码上付出更高的编译代价。TurboFan 则是对罕用代码(热点代码)进行本地化编译,生成体系结构相干的机器码,这须要更长的编译工夫,换来的是更快的执行速度。
去优化,是将机器码转成字节码,为什么要这样做?起因有很多,具体起因参见 TurboFan 的定义文件。这里说一个与技术开发人员相干的起因:调试 javascript 源码,对源码进行调试时,须要转回字节码。
3. 字节码生成
聊字节码生成之前,先要看明确 AST 树的构造,明确了 AST 树结构,也就晓得了字节码生成其实是遍历树的过程,落地到程序上就是一个无限状态自动机,具体实现就是 switch case
配合一些预设的宏定义模板,图 5 给出了 AST 的数据结构。
AST 树的每个节点都继承自 AstNode
这个类,能够说所有皆“AstNode”。AstNode
的成员办法是最多的,在泛滥办法中,AstNode 的 NodeType
办法无疑是最重要的,因为把一个 AstNode 节点翻译成字节码时,首先,依据 NodeType
把父类 AstNode 转成具体的子类,比方,转成表达式 (ExPRESSION) 或语句(STATEMENT);其次,能力读取相应的数据、生成字节码,上面的代码是 AstNode 转成 Assignment 的具体实现。
void BytecodeGenerator::VisitAssignment(Assignment* expr) {AssignmentLhsData lhs_data = PrepareAssignmentLhs(expr->target());
VisitForAccumulatorValue(expr->value());
builder()->SetExpressionPosition(expr);
BuildAssignment(lhs_data, expr->op(), expr->lookup_hoisting_mode());
}
在这段代码中,计算 expr->target(),expr->value(),expr->op()
时可能会产生递归调用,因为表达式内能够蕴含多个子表达式。
void BytecodeGenerator::GenerateBytecodeBody() {
// Build the arguments object if it is used.
VisitArgumentsObject(closure_scope()->arguments());
// Build rest arguments array if it is used.
Variable* rest_parameter = closure_scope()->rest_parameter();
VisitRestArgumentsArray(rest_parameter);
// Build assignment to the function name or {.this_function}
// variables if used.
VisitThisFunctionVariable(closure_scope()->function_var());
VisitThisFunctionVariable(closure_scope()->this_function_var());
// Build assignment to {new.target} variable if it is used.
VisitNewTargetVariable(closure_scope()->new_target_var());
// Create a generator object if necessary and initialize the
// {.generator_object} variable.
FunctionLiteral* literal = info()->literal();
if (IsResumableFunction(literal->kind())) {BuildGeneratorObjectVariableInitialization();
}
// Emit tracing call if requested to do so.
if (FLAG_trace) builder()->CallRuntime(Runtime::kTraceEnter);
// Emit type profile call.
if (info()->flags().collect_type_profile()) {feedback_spec()->AddTypeProfileSlot();
int num_parameters = closure_scope()->num_parameters();
for (int i = 0; i < num_parameters; i++) {Register parameter(builder()->Parameter(i));
builder()->LoadAccumulatorWithRegister(parameter).CollectTypeProfile(closure_scope()->parameter(i)->initializer_position());
}
}
// Increment the function-scope block coverage counter.
BuildIncrementBlockCoverageCounterIfEnabled(literal, SourceRangeKind::kBody);
// Visit declarations within the function scope.
if (closure_scope()->is_script_scope()) {VisitGlobalDeclarations(closure_scope()->declarations());
} else if (closure_scope()->is_module_scope()) {VisitModuleDeclarations(closure_scope()->declarations());
} else {VisitDeclarations(closure_scope()->declarations());
}
// Emit initializing assignments for module namespace imports (if any).
VisitModuleNamespaceImports();
// The derived constructor case is handled in VisitCallSuper.
if (IsBaseConstructor(function_kind())) {if (literal->class_scope_has_private_brand()) {BuildPrivateBrandInitialization(builder()->Receiver());
}
if (literal->requires_instance_members_initializer()) {BuildInstanceMemberInitialization(Register::function_closure(),
builder()->Receiver());
}
}
// Visit statements in the function body.
VisitStatements(literal->body());
// Emit an implicit return instruction in case control flow can fall off the
// end of the function without an explicit return being present on all paths.
if (!builder()->RemainderOfBlockIsDead()) {builder()->LoadUndefined();
BuildReturn(literal->return_position());
}
}
下面的函数是生成 bytecode 的入口,最终进入 VisitStatements(literal->body());
,从这里开始生成 bytecode,在生成 byteocde 之前要先应用AstNode->XXXtype()
获取子类的具体类型,上面给出 XXXtype
的具体实现。
#define DECLARATION_NODE_LIST(V) \
V(VariableDeclaration) \
V(FunctionDeclaration)
#define ITERATION_NODE_LIST(V) \
V(DoWhileStatement) \
V(WhileStatement) \
V(ForStatement) \
V(ForInStatement) \
V(ForOfStatement)
#define BREAKABLE_NODE_LIST(V) \
V(Block) \
V(SwitchStatement)
#define STATEMENT_NODE_LIST(V) \
ITERATION_NODE_LIST(V) \
BREAKABLE_NODE_LIST(V) \
V(ExpressionStatement) \
V(EmptyStatement) \
V(SloppyBlockFunctionStatement) \
V(IfStatement) \
V(ContinueStatement) \
V(BreakStatement) \
V(ReturnStatement) \
V(WithStatement) \
V(TryCatchStatement) \
V(TryFinallyStatement) \
V(DebuggerStatement) \
V(InitializeClassMembersStatement) \
V(InitializeClassStaticElementsStatement)
#define LITERAL_NODE_LIST(V) \
V(RegExpLiteral) \
V(ObjectLiteral) \
V(ArrayLiteral)
#define EXPRESSION_NODE_LIST(V) \
LITERAL_NODE_LIST(V) \
V(Assignment) \
V(Await) \
V(BinaryOperation) \
//............ 代码太长,省略很多
V(YieldStar)
#define FAILURE_NODE_LIST(V) V(FailureExpression)
#define AST_NODE_LIST(V) \
DECLARATION_NODE_LIST(V) \
STATEMENT_NODE_LIST(V) \
EXPRESSION_NODE_LIST(V)
//========= 分隔线 ===============================
#define GENERATE_VISIT_CASE(NodeType) \
case AstNode::k##NodeType: \
return this->impl()->Visit##NodeType(static_cast<NodeType*>(node));
#define GENERATE_FAILURE_CASE(NodeType) \
case AstNode::k##NodeType: \
UNREACHABLE();
//========= 分隔线 ===============================
#define GENERATE_AST_VISITOR_SWITCH() \
switch (node->node_type()) { \
AST_NODE_LIST(GENERATE_VISIT_CASE) \
FAILURE_NODE_LIST(GENERATE_FAILURE_CASE) \
}
#define DEFINE_AST_VISITOR_SUBCLASS_MEMBERS() \
public: \
void VisitNoStackOverflowCheck(AstNode* node) { \
GENERATE_AST_VISITOR_SWITCH() \} \
\
void Visit(AstNode* node) { \
if (CheckStackOverflow()) return; \
VisitNoStackOverflowCheck(node); \
} \
上述代码中,隔开的三局部代码,组成了 AstNode 中所有类型 (NodeType) 的 switch 语句,第一局部代码和图 5 的节点类型一一对应。
void BytecodeGenerator::VisitStatements(const ZonePtrList<Statement>* statements) {for (int i = 0; i < statements->length(); i++) {
// Allocate an outer register allocations scope for the statement.
RegisterAllocationScope allocation_scope(this);
Statement* stmt = statements->at(i);
Visit(stmt);
if (builder()->RemainderOfBlockIsDead()) break;
}
}
上述代码是 bytecode 生成的入口,请读者应用图 1 的样例代码自行跟踪,图 6 给出 VisitStatements
的函数调用堆栈。
V8 中 AST 到字节码的翻译过程,与编译 LLVM 中 AST 到三地址码的翻译类似,读者可自行查阅编译技术相干材料。
好了,明天到这里,下次见。
恳请读者批评指正、提出宝贵意见
微信:qq9123013 备注:v8 交换 邮箱:v8blink@outlook.com