JavaScriptCore引擎深度解析5-字节码生成篇(中)

前言

上一节讲到继承自ScopeNode的4种NodeProgramNodeEvalNodeFunctionNodeModuleProgramNode,它们的字节码生成最终均走到了emitStatementsBytecode方法中。

字节码指令集

在分析之前,先大概了解下字节码的长相:

{
        "section" : "Bytecodes", "emitInHFile" : true, "emitInASMFile" : true, 
        "macroNameComponent" : "BYTECODE", "asmPrefix" : "llint_", 
        "bytecodes" : [
            { "name" : "op_enter", "length" : 1 },
            { "name" : "op_get_scope", "length" : 2 },
            { "name" : "op_create_direct_arguments", "length" : 2 },
            { "name" : "op_create_scoped_arguments", "length" : 3 },
            { "name" : "op_create_cloned_arguments", "length" : 2 },
            { "name" : "op_create_this", "length" : 5 },
            { "name" : "op_to_this", "length" : 4 },
            { "name" : "op_check_tdz", "length" : 2 },
            { "name" : "op_new_object", "length" : 4 },
            { "name" : "op_new_array", "length" : 5 },
            { "name" : "op_new_array_with_size", "length" : 4 },
            { "name" : "op_new_array_buffer", "length" : 5 },
            { "name" : "op_new_regexp", "length" : 3 },
            { "name" : "op_mov", "length" : 3 },
            { "name" : "op_not", "length" : 3 },
            { "name" : "op_eq", "length" : 4 },
            ...

可以看到指令的名字和长度,最后生成的字节码就是这样的指令集合。这部分内容在JavaScriptCore/bytecode/BytecodeList.json可以看到完整的指令集

emitStatementsBytecode

来看下我们期待很久的emitStatementsBytecode吧,它是ScopeNode的一个方法

inline void ScopeNode::emitStatementsBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    if (!m_statements) return;
    m_statements->emitBytecode(generator, dst);
}

m_statements的类型为SourceElements,定义如下面的代码所示:它是一个单向链表,里面的节点类型为StatementNode,这意味着存储的节点可以是StatementNode类型,也可以是StatementNode子类型。同时也说明,ScopeNode由一个个串起来的StatementNode组成:

class SourceElements final : public ParserArenaFreeable {
    public:
        SourceElements();
        void append(StatementNode*);
        void emitBytecode(BytecodeGenerator&, RegisterID* destination);

    private:
        StatementNode* m_head;
        StatementNode* m_tail;
    };

可以看到,循环遍历该链表,对其中的每一个节点,生成其对应的字节码

inline void SourceElements::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    for (StatementNode* statement = m_head; statement; statement = statement->next())
        generator.emitNodeInTailPosition(dst, statement);
}

void emitNodeInTailPosition(RegisterID* dst, StatementNode* statementNode)
{
    // Node::emitCode assumes that dst, if provided, is either a local or a referenced temporary.
    statementNode->emitBytecode(*this, dst);
}

这里的statementNode的类型可以是下面Node中的任意一种,这些Node均继承自StatementNode

IfElseNode
DoWhileNode
WhileNode
ForNode
SwitchNode
ClassDecNode
DeclarationStatement
ForInNode
ForOfNode
ContinueNode
BreakNode
ReturnNode
FuncDeclNode
BlockNode
ModuleDeclarationNode
...

StatementNode


StatementNode主要是用来描述语言结构:
(1)顺序结构:ClassDeclNodeDeclarationStateMentNode
(2)条件结构:IFElseNodeSwitchNode
(3)循环结构:WhileNodeForNodeDoWhileNode

接下里将挑选一些Node作为代表来进行分析字节码的生成

回填思想
除了顺序结构,条件结构和循环结构本身不会产生具体的逻辑指令,它们主要生成一些跳转指令,来控制程序流的走向,从而达到一定的目的。然而生成一个跳转指令时,可能并不能立刻知晓跳转的目的地,所以暂时不指定该跳转指令的目标标号,等到能够确定正确的目标标号时,才去填充这些指令的目标标号,这种技术叫做回填技术。

条件结构

// Returns the next available temporary register. Registers returned by
        // newTemporary require a modified form of reference counting: any
        // register with a refcount of 0 is considered "available", meaning that
        // the next instruction may overwrite it.

IfElseNode

在语法分析篇中提到:语法分析在parseIfStatement中对IfStatement做了规范化处理,这里看下它是如何生成字节码的。

void IfElseNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    // generator.newLabel 生成一个用于存放标号的 新的临时变量L,返回变量地址
    // 
    RefPtr<Label> beforeThen = generator.newLabel();
    RefPtr<Label> beforeElse = generator.newLabel();
    RefPtr<Label> afterElse = generator.newLabel();

    Label* trueTarget = beforeThen.get();
    Label* falseTarget = beforeElse.get();
    FallThroughMode fallThroughMode = FallThroughMeansTrue;

    // 如果是break和continue
    bool didFoldIfBlock = tryFoldBreakAndContinue(generator, m_ifBlock, trueTarget, fallThroughMode);

    // 生成条件表达式m_condition的字节码,可能会生成类似goto的指令
    // 如果m_condition为真,goto trueTarget;否则 goto falseTarget
    // 然而此时trueTarget和falseTarget的值还未能明确知道,需要等到整个
    // if-else模块完毕,才可能得到明确的值,这里就会用到回填技术
    generator.emitNodeInConditionContext(m_condition, trueTarget, falseTarget, fallThroughMode);

    // 回填beforeThen的地址,标记m_condition为真的跳转指令
    // 注意:这里只是修改beforeThen的值,并没有添加任何指令
    generator.emitLabel(beforeThen.get());

    // 如果是正常的if-else
    if (!didFoldIfBlock) {
        // 生成if-block的字节码
        generator.emitNodeInTailPosition(dst, m_ifBlock);

        // if模块执行完毕,跳转到afterElse,也就是整个if-else模块的下一条指令
        // ** 注意:这里是一条跳转指令(goto afterElse)**
        if (m_elseBlock)
            generator.emitJump(afterElse.get());
    }

    // 填写beforeElse的地址,标记m_condition为假的跳转指令,也就是else模块的第一条指令
    generator.emitLabel(beforeElse.get());

    // 如果有else-block,生成其字节码
    if (m_elseBlock) {
        generator.emitNodeInTailPosition(dst, m_elseBlock);
    }

    // 回填afterElse的地址,就是整个if-else模块的下一条指令
    generator.emitLabel(afterElse.get());
}

看下IfElseNode的SDT:

SwitchNode

SwitchNode肯定都比较熟悉了,来看下它是如何生成字节码的:

void SwitchNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    LabelScopePtr scope = generator.newLabelScope(LabelScope::Switch);

    RefPtr<RegisterID> r0 = generator.emitNode(m_expr);

    // 把自己作为一个scope进行push
    generator.pushLexicalScope(this, BytecodeGenerator::TDZCheckOptimization::DoNotOptimize, BytecodeGenerator::NestedScopeType::IsNested);

    // 生成switch{}里面的字节码
    m_block->emitBytecodeForBlock(generator, r0.get(), dst);

    // pop scope
    generator.popLexicalScope(this);

    // 回填scope的地址,因为switch中的break的跳转地址就是switch模块的下一个地址
    // 这里SwitchNode已经分析完毕,所以就可以回填其地址
    generator.emitLabel(scope->breakTarget());

}

switch{}中包含了若干case语句和defalut语句

enum SwitchType 
{ 
    SwitchNone,
    SwitchImmediate, 
    SwitchCharacter, 
    SwitchString 
};

void CaseBlockNode::emitBytecodeForBlock(BytecodeGenerator& generator, RegisterID* switchExpression, RegisterID* dst)
{
    RefPtr<Label> defaultLabel;
    Vector<RefPtr<Label>, 8> labelVector;
    Vector<ExpressionNode*, 8> literalVector;
    int32_t min_num = std::numeric_limits<int32_t>::max();
    int32_t max_num = std::numeric_limits<int32_t>::min();

    // 确认switchType
    SwitchInfo::SwitchType switchType = tryTableSwitch(literalVector, min_num, max_num);

    if (switchType != SwitchInfo::SwitchNone) 
    {
        // 如果是SwitchImmediate, SwitchCharacter, SwitchString,生成对应的case跳转标签和defalut标签
        for (uint32_t i = 0; i < literalVector.size(); i++)
            labelVector.append(generator.newLabel());
        defaultLabel = generator.newLabel();

        // 生成op_switch_imm、op_switch_char、op_switch_string指令,用于比较
        generator.beginSwitch(switchExpression, switchType);
    } 
    else 
    {
        // 如果是SwitchNone,设置各种跳转
        for (ClauseListNode* list = m_list1; list; list = list->getNext()) 
        {
            RefPtr<RegisterID> clauseVal = generator.newTemporary();
            // 生成常量表达式的字节码
            generator.emitNode(clauseVal.get(), list->getClause()->expr());
            // 比较参数和常量表达式的字节码
            generator.emitBinaryOp(op_stricteq, clauseVal.get(), clauseVal.get(), switchExpression, OperandTypes());
            // 生成一个标签,用于跳转
            labelVector.append(generator.newLabel());
            // 如果比较结果,跳转到对应的标签
            generator.emitJumpIfTrue(clauseVal.get(), labelVector[labelVector.size() - 1].get());
        }

        // 同上
        for (ClauseListNode* list = m_list2; list; list = list->getNext()) {
            RefPtr<RegisterID> clauseVal = generator.newTemporary();
            generator.emitNode(clauseVal.get(), list->getClause()->expr());
            generator.emitBinaryOp(op_stricteq, clauseVal.get(), clauseVal.get(), switchExpression, OperandTypes());
            labelVector.append(generator.newLabel());
            generator.emitJumpIfTrue(clauseVal.get(), labelVector[labelVector.size() - 1].get());
        }

        // 为defalut标签设置跳转
        defaultLabel = generator.newLabel();
        generator.emitJump(defaultLabel.get());
    }

    size_t i = 0;
    // default前面的case
    for (ClauseListNode* list = m_list1; list; list = list->getNext()) {
        // 回填case标签地址
        generator.emitLabel(labelVector[i++].get());
        // case语句生成字节码
        list->getClause()->emitBytecode(generator, dst);
    }

    if (m_defaultClause) {
        // 回填default标签地址
        generator.emitLabel(defaultLabel.get());
        // default语句生成字节码
        m_defaultClause->emitBytecode(generator, dst);
    }

    // default后面的case
    for (ClauseListNode* list = m_list2; list; list = list->getNext()) {
        generator.emitLabel(labelVector[i++].get());
        list->getClause()->emitBytecode(generator, dst);
    }

    // 如果没有defalut,设置其跳转地址为switch结束的地址
    if (!m_defaultClause)
        generator.emitLabel(defaultLabel.get());

    if (switchType != SwitchInfo::SwitchNone) {
        generator.endSwitch(labelVector.size(), labelVector.data(), literalVector.data(), defaultLabel.get(), min_num, max_num);
    }
}
swtich E
    begin
        case V1 : S1
        case V2: S2
        ...
        case Vn-1 : Sn-1
        default : Sn
    End

普通情况下,可能都会像这样按照顺序翻译SwitchNode(这种方式比较低效)

JavaScriptCoreSwitchNode采取了另一种翻译方式:在代码生成阶段,如果分支的个数在一个较小的范围内,那么这种条件跳转 指令序列可以被翻译成最高效的n路分支。

可以看到SwitchNone的跳转表不同于其它的3种方式,跳转表提前了,不过这并没有什么本质上的不同

循环结构

WhileNode

void WhileNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    // 生成一个scope标签,用于continue和break
    LabelScopePtr scope = generator.newLabelScope(LabelScope::Loop);

    // 生成循环体的起始地址标签
    RefPtr<Label> topOfLoop = generator.newLabel();

    // 生成条件表达式的字节码,如果为真,跳转到topOfLoop,否则跳转到breakTarget
    generator.emitNodeInConditionContext(m_expr, topOfLoop.get(), scope->breakTarget(), FallThroughMeansTrue);

    // 回填起始地址
    generator.emitLabel(topOfLoop.get());

    // 插入op_loop_hint循环指令
    generator.emitLoopHint();

    // 生成循环体的字节码
    generator.emitNodeInTailPosition(dst, m_statement);

    // 回填continueTarget的地址
    generator.emitLabel(scope->continueTarget());

    // 生成条件表达式的字节码,如果为真,跳转到topOfLoop,否则跳转到breakTarget
    generator.emitNodeInConditionContext(m_expr, topOfLoop.get(), scope->breakTarget(), FallThroughMeansFalse);

    // 回填breakTarget的地址
    generator.emitLabel(scope->breakTarget());

}

来看下WhileNode的SDT

在循环中经常会看到break和continue,来看看他们的字节码如何生成

BreakNode

void BreakNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
{
    LabelScopePtr scope = generator.breakTarget(m_ident);
    generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
    generator.emitJump(scope->breakTarget());
}

ContinueNode

void ContinueNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
{
    LabelScopePtr scope = generator.continueTarget(m_ident);
    generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
    generator.emitJump(scope->continueTarget());
}

可以看到这两者最后都是加了跳转指令,跳转到其所属的scope的breakTarget或continueTarget。
另外还可以看到对区域进行了pop,这也容易理解,毕竟break和continue在一定的场合下(循环结构和switch条件结构中)才能被使用,如果触发这二者,可能会跳出当前的一个switch区域或者循环区域,直接离开了当前区域。

顺序结构

不同于前两种结构,顺序结构将会明显地看到更多更详细的字节码

ClassDeclNode

类的声明节点ClassDeclNode的字节码生成语句仅仅只有一行熟悉的代码:

void ClassDeclNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    generator.emitNode(dst, m_classDeclaration);
}

其中m_classDeclaration的类型是ClassExprNode,它是ExpressionNode的一个子类,该表达式的字节码生成在后文中将会作详细的分析。

ReturnNode

void ReturnNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    // 方法忽略返回值
    if (dst == generator.ignoredResult()) dst = 0;

    // 生成return后的表达式的字节码
    RefPtr<RegisterID> returnRegister = m_value ? 
    generator.emitNodeInTailPosition(dst, m_value) : 
    generator.emitLoad(dst, jsUndefined());

    ...
    // 
    generator.emitReturn(returnRegister.get());
}
RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
{
    // 实际上这里还有对构造函数的特殊处理
    ...

    // 生成一元操作符字节码
    return emitUnaryNoDstOp(op_ret, src);
}
RegisterID* BytecodeGenerator::emitUnaryNoDstOp(OpcodeID opcodeID, RegisterID* src)
{
    emitOpcode(opcodeID);
    instructions().append(src->index());
    return src;
}

可以看到,在instructions插入了一条op_ret指令,后面的src->index()src在虚拟寄存器VirtualRegister的偏移值

LabelNode

LabelNode的字节码比较简单,主要是生成一个跳转标签

void LabelNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
    LabelScopePtr scope = generator.newLabelScope(LabelScope::NamedLabel, &m_name);
    generator.emitNodeInTailPosition(dst, m_statement);
    generator.emitLabel(scope->breakTarget());
}

FuncDeclNode

函数的声明实际上就是要进行一个函数名字的注册

void FuncDeclNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
{
    generator.hoistSloppyModeFunctionIfNecessary(metadata()->ident());
}

void BytecodeGenerator::hoistSloppyModeFunctionIfNecessary(const Identifier& functionName)
{
    if (m_scopeNode->hasSloppyModeHoistedFunction(functionName.impl()))
    {
        Variable currentFunctionVariable = variable(functionName);
        RefPtr<RegisterID> currentValue;
        if (RegisterID* local = currentFunctionVariable.local())
            currentValue = local;
        else 
        {
            RefPtr<RegisterID> scope = emitResolveScope(nullptr, currentFunctionVariable);
            currentValue = emitGetFromScope(newTemporary(), scope.get(), currentFunctionVariable, DoNotThrowIfNotFound);
        }

        SymbolTableStackEntry& varScope = m_symbolTableStack[*m_varScopeSymbolTableIndex];
        SymbolTable* varSymbolTable = varScope.m_symbolTable;

        ConcurrentJITLocker locker(ConcurrentJITLocker::NoLockingNecessary);
        SymbolTableEntry entry = varSymbolTable->get(locker, functionName.impl());
        bool isLexicallyScoped = false;
        emitPutToScope(varScope.m_scope, variableForLocalEntry(functionName, entry, varScope.m_symbolTableConstantIndex, isLexicallyScoped), currentValue.get(), DoNotThrowIfNotFound, InitializationMode::NotInitialization);
    }
}

未完待续

-------------本文结束 感谢您的阅读-------------

本文标题:JavaScriptCore引擎深度解析5-字节码生成篇(中)

文章作者:lingyun

发布时间:2018年08月15日 - 00:08

最后更新:2018年11月09日 - 02:11

原始链接:https://tsuijunxi.github.io/2018/08/15/JavaScriptCore引擎深度解析-5-字节码生成篇(中)/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。