v8

649 阅读7分钟

1.前置知识

高级语言本质:分离逻辑的表达与实现细节。程序员只用专注逻辑表达,通过编译器/解释器来转换为带有执行细节的机器代码; 语言只是用来表达逻辑,具体实现通过其api的封装,比如node的fs模块,通过c++扩展实现行为细节。

c语言执行过程:预编译 cpp(.i头文件插入). ->编译 gcc(.s汇编语言) ->汇编 as(.o目标文件) ->链接 ld(可执行文件)

编译:从一门编程语言转为另一门编程语言,从高级语言转换成低级语言,或者从高级语言到高级语言

image.png

  • 转换原则:语义等价”,通过一门语言解释另一门语言,不能丢失或者添加一些语义,一定要前后一致才可以;

  • 转换格式:词法、语法,整体叫做文法;

  • 词法分析:token识别。按照状态机来分的(有限状态机 DFA
    image.png

  • 语法分析:递归下降LL(1),第1个L表明自顶向下分析是从左向右扫描输入串,第2个L表明分析过程中将用最左到右推倒,1表明只需向右看一个符号便可决定如何推倒;

  • 语法制导翻译: ll分析是在递归的过程中进行语义分析;用线性IR中的指令来翻译AST节点的属性

  • 中间代码:ast(js),bytecode(java)、tac三地址码、P_code、SSA;

  • 运行存储:在运行时刻动态堆栈分配、在编译时刻静态顺序层次分配;建立活动记录、活动树、访问链、符号表;

  • 代码优化、静态分析:线性IR的分析要建立流图,就是控制流图,控制流就是根据if、while、函数调用等导致的程序跳转,把顺序执行的代码和跳转到的代码之间连接起来就是一个图,顺序执行的代码看成一个整体,叫做基本快。之后根据这个流图做数据流分析,也就是分析一个变量流经了那些代码,然后基于这些做各种优化

  • 代码生成:选择合适的目标机器指令来实现中间表示IR语句;

  • 动态、静态链接: 将elf文件链接成一个文件;

高级语言的代码都是嵌套的,有函数、if、else、while等各种块,块之间又可以嵌套;
低级语言比如汇编,就是一条条指令,线性的结构。

2.分类及应用

首先会依次对源代码进行词法分析、语法分析,生成抽象语法树(AST)

分类过程名词
编译器优化代码,最后再生成处理器能够理解的机器码parser/acorn
转译器把树形的ast转换为另一个ast,然后再打印成目标代码的字符串transform/babel
解释器基于ast递归节点属性、解释执行interpret

前端领域应用:

  • 工程化领域各种转译器: babel、typescript、eslint、terser、prettier、postcss、posthtml、taro、vue template compiler等

  • js引擎: v8、javascriptcore、quickjs、hermes等

  • wasm: llvm可以生成wasm字节码,所以c++、rust等可以转为llvm ir的语言都可以做wasm开发

  • ide 的 lsp: 编程语言的语法高亮、智能提示、错误检查等通过language service protocol协议来通信,而lsp服务端主要是基于parser对正在编辑的文本做分析

image.png

3.V8编译过程

image.png

编译器:将源代码编译成抽象语法树

  • 第 1 阶段是分词(tokenize),称为词法分析,其作用是将一行行的源码拆解成一个个token;
  • 第 2 阶段是解析(parse),又称为语法分析,其作用是将上一步生成的token 数据,根据语法规则转为AST。

如果源码符合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误

image.png

  • Parser:解释源代码并构建成ast;
  • FullCodeGenerator:通过遍历ast来为js生成不同平台的汇编语言;

解释器:接收字节码,解释执行字节码,同时依赖垃圾回收

用一门高级语言来解释另一门高级语言的中间程序,比如c++,一般都用c++来写解释器,因为可以做内存管理;

  • tree walker解释器,直接遍历然后用c++来执行不同的节点、解释执行ast

v8引擎在17年之前都是这么干的。但是在17年之后引入了字节码,因为字节码可以缓存,这样下次再直接执行字节码就不需要parse了。字节码是种线性结构,也要做ast到线性ir的转换,之后在vm上执行字节码。

  • 虚拟机:一般解释线性代码的比如汇编代码、字节码等这种的程序

  • v8引擎代码执行过程 image.png

  • v8引擎运行js主要的类
    image.png

JSFunction:执行js;
Runtime:辅助类,属性访问、类型转换、编译、正则等;

  • 解释执行,它会根据AST生成字节码,并解释执行字节码

为了解决内存占用问题,V8团队大幅重构了引擎架构,引入字节码,并且抛弃了之前的编译器

  • 编译执行 : 如果有一段第一次执行的字节码,解析器会逐条解释执行,在执行字节码的过程中,如果发现有热点代码(一段代码被重复执行多次,称为热点代码),那么后台的编译器就会把这段热点代码的字节码编译为高效的机器码,当再次执行这段被优化的代码的时候,只需要执行编译后的机器码

image.png

  • 动态编译(dynamic compilation)指的是“在运行时进行编译”;
  • 事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation);
  • JIT编译(just-in-time compilation)当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;

4. 实现原理

4.1 编译器parser

源码主要分三部分:

  • tokenizer解析获取token,包括state、context
  • parser 包含node、statement、expression
  • plugins帮助解析ts、jsx等语法

编译过程:

  • enterInitialScopes:进入初始化的作用域
  • startNode:创建File节点和Program节点,new Node()
  • nextToken:去获取一个token。readWord -> finishToken
  • parseTopLevel:递归去解析。parseProgram -> parseBlockBody -> parseBlockOrModuleBlockBody -> parseStatement -> parseStatementContext

深度优先去遍历这棵ast树,不同节点执行各自parser方法;每个节点都会生成一个Node,startNode、finishNode,startToken、finishToken是成对存在的

export default class Parser extends StatementParser {
  parse(): File {
    this.enterInitialScopes();
    const file = this.startNode();
    const program = this.startNode();
    this.nextToken();
    file.errors = null;
    this.parseTopLevel(file, program);
    file.errors = this.state.errors;
    return file;
  }
}

4.2 解释器interpret

  • 节点处理器
const nodeHandler = {
  Program () {},  //
  //变量定义
  VariableDeclaration (nodeIterator) {
    const kind = nodeIterator.node.kind
    for (const declaration of nodeIterator.node.declarations) {
      const { name } = declaration.id
      const value = declaration.init ? nodeIterator.traverse(declaration.init) : undefined
      // 在作用域当中定义变量
      // 如果当前是块级作用域且变量用var定义,则定义到父级作用域
      if (nodeIterator.scope.type === 'block' && kind === 'var') {
        nodeIterator.scope.parentScope.declare(name, value, kind)
      } else {
        nodeIterator.scope.declare(name, value, kind)
      }
    }
  },
  ExpressionStatement () {},   //函数表达式
  MemberExpression () {},  //person.say这种函数
  CallExpression () {},  //函数表达式
  Identifier () {}  // 获取标识符的值
}
  • 节点遍历器

class NodeIterator {
  constructor (node, scope = {}) {
    this.node = node
    this.scope = scope
    this.nodeHandler = nodeHandler
  }

  traverse (node, options = {}) {
    const scope = options.scope || this.scope
    const nodeIterator = new NodeIterator(node, scope)
    const _eval = this.nodeHandler[node.type]
    if (!_eval) {
      throw new Error(`canjs: Unknown node type "${node.type}".`)
    }
    return _eval(nodeIterator)
  }

  createScope (blockType = 'block') {
    return new Scope(blockType, this.scope)
  }
}

  • 作用域基类

class Scope {
  constructor (type, parentScope) {
    // 作用域类型,区分函数作用域function和块级作用域block
    this.type = type
    // 父级作用域
    this.parentScope = parentScope
    // 全局作用域
    this.globalDeclaration = standardMap
    // 当前作用域的变量空间
    this.declaration = Object.create(null)
  }

  /*
   * get/set方法用于获取/设置当前作用域中对应name的变量值
     符合JS语法规则,优先从当前作用域去找,若找不到则到父级作用域去找,然后到全局作用域找。
     如果都没有,就报错
   */
  get (name) {
    if (this.declaration[name]) {
      return this.declaration[name]
    } else if (this.parentScope) {
      return this.parentScope.get(name)
    } else if (this.globalDeclaration[name]) {
      return this.globalDeclaration[name]
    }
    throw new ReferenceError(`${name} is not defined`)
  }

  set (name, value) {
    if (this.declaration[name]) {
      this.declaration[name] = value
    } else if (this.parentScope[name]) {
      this.parentScope.set(name, value)
    } else {
      throw new ReferenceError(`${name} is not defined`)
    }
  }

  /**
   * 根据变量的kind调用不同的变量定义方法
   */
  declare (name, value, kind = 'var') {
    if (kind === 'var') {
      return this.varDeclare(name, value)
    } else if (kind === 'let') {
      return this.letDeclare(name, value)
    } else if (kind === 'const') {
      return this.constDeclare(name, value)
    } else {
      throw new Error(`canjs: Invalid Variable Declaration Kind of "${kind}"`)
    }
  }

  varDeclare (name, value) {
    let scope = this
    // 若当前作用域存在非函数类型的父级作用域时,就把变量定义到父级作用域
    while (scope.parentScope && scope.type !== 'function') {
      scope = scope.parentScope
    }
    this.declaration[name] = new SimpleValue(value, 'var')
    return this.declaration[name]
  }

  letDeclare (name, value) {
    // 不允许重复定义
    if (this.declaration[name]) {
      throw new SyntaxError(`Identifier ${name} has already been declared`)
    }
    this.declaration[name] = new SimpleValue(value, 'let')
    return this.declaration[name]
  }

  constDeclare (name, value) {
    // 不允许重复定义
    if (this.declaration[name]) {
      throw new SyntaxError(`Identifier ${name} has already been declared`)
    }
    this.declaration[name] = new SimpleValue(value, 'const')
    return this.declaration[name]
  }
}

解释器

const { Parser } = require('acorn')
const NodeIterator = require('./iterator')
const Scope = require('./scope')

class Canjs {
  constructor (code = '', extraDeclaration = {}) {
    this.code = code
    this.extraDeclaration = extraDeclaration
    this.ast = Parser.parse(code)
    this.nodeIterator = null
    this.init()
  }

  init () {
    // 定义全局作用域,该作用域类型为函数作用域
    const globalScope = new Scope('function')
    // 根据入参定义标准库之外的全局变量
    Object.keys(this.extraDeclaration).forEach((key) => {
      globalScope.addDeclaration(key, this.extraDeclaration[key])
    })
    this.nodeIterator = new NodeIterator(null, globalScope)
  }

  run () {
    return this.nodeIterator.traverse(this.ast)
  }
}

4.3 转译器transform

babel转译器

参考链接

推荐文章

欢迎关注我的前端自检清单,我和你一起成长