Давайте разберемся в Chrome V8 — Глава 6: Генерация байткода

Оригинальный источник: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

Оцените статью
devanswers.ru
Добавить комментарий