Оригинальный источник:https://medium.com/@huidou/lets-understand-chrome-v8-chapter-6-bytecode-generation-cd189b671a9
Добро пожаловать в другие главы «Давайте поймем Chrome V8».
Байткод — это результат разбора, который представляет собой независимый от архитектуры абстрактный машинный код. В этой статье мы начнем отладку с AST, объясним генерацию байткода, проанализируем код ядра и важные структуры данных, как показано на рисунке 1.
1. Введение
V8 имеет сотни байткодов, начиная от простых операций, таких как Add и Sub, до сложных операций, таких как LdaNamedProperty. Каждый байткод может использовать регистры и аккумулятор в качестве операндов. Аккумулятор — это обычный регистр, как и любой другой регистр, но разница в том, что чтение/запись аккумулятора неявная.
Например: 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)
//.........omit.....
Приведенный выше код — это макроопределение байткода. Давайте поговорим о V(Ldar, ImplicitRegisterUse::kWriteAccumulator, OperandType::kReg), Ldar означает загрузку данных в аккумулятор, ImplicitRegisterUse::kWriteAccumulator и OperandType::kReg — операнд источника и операнд назначения соответственно. См. пример ниже:.
- LdaSmi [1], [1] — это маленький int, который будет добавлен в аккумулятор, как показано на рисунке 2.
- Star r1, r1 — это общий регистр, в который будет записано значение аккумулятора, как показано на рисунке 3.
Для других инструкций байткода, пожалуйста, обратитесь к файлу определения инструкций V8.
Для повышения производительности V8 помечает байткод с высокой частотой выполнения как горячий код и использует TurboFan для компиляции кода в локальный машинный код, как показано на рисунке 4.
Компиляция TurboFan (из байткода в локальный машинный код) требует больше времени и ресурсов, поэтому подходит только для горячего кода. Интерпретерная компиляция (из JS в байткод) требует очень мало времени и ресурсов и подходит для общих случаев.
Как правило, большое количество выполнений приводит к преобразованию байткодов в горячие коды.
Но какова причина понижения горячего кода до байткода? Есть много причин, общей причиной является отладка — — вы открываете F12 для отладки JS.
2. Генерация байткода
Прежде чем переходить к байткоду, нам нужно знать дерево AST, потому что генерация байткода — это процесс хождения по дереву AST. В V8 хождение по AST является конечным автоматом состояния, который вместе с некоторыми предопределенными шаблонами макросов генерирует байткод. На рисунке 5 показана структура данных AST.
Все узлы дерева AST наследуются от родительского класса 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());
}
}
Приведенный выше код является входом для генерации байткода и, наконец, входит в VisitStatements(literal->body()), который отвечает за генерацию байткода.
Перед генерацией байткода необходимо извлечь тип подкласса, ниже приведен AstNode->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)
//=========separation===============================
#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();
//=========separation===============================
#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 состоит из трех вышеприведенных частей кода. Первая часть кода соответствует рисунку 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;
}
}
Приведенный выше код является входной функцией генерации байткода. Рисунок 6 — стек вызовов VisitStatements.
Итак, на этом мы заканчиваем эту статью. Увидимся в следующий раз, ребята, всего доброго!
Мой блог — cncyclops.com. Пожалуйста, свяжитесь со мной, если у вас возникнут какие-либо вопросы.
WeChat: qq9123013 Email: v8blink@outlook.com