Prepack

使JavaScript代码运行速度更快的工具。

*Prepack还处于早期发展阶段,尚未准备好进行生产使用。请尝试一下,提供反馈并帮助修复错误。

它有什么作用?

Prepack是一个优化JavaScript源代码的工具:可以在编译时而不是运行时完成计算的消除。Prepack用等效的代码替换了JavaScript bundle的全局代码,这是一个简单的分配顺序。这消除了大多数中间计算和对象分配。

示例

Hello World

(function () {
  function hello() { return 'hello'; }
  function world() { return 'world'; }
  global.s = hello() + ' ' + world();
})();
(function () {
  s = "hello world";
})();

Elimination of abstraction tax

(function () {
  var self = this;
  ['A', 'B', 42].forEach(function(x) {
    var name = '_' + x.toString()[0].toLowerCase();
    var y = parseInt(x);
    self[name] = y ? y : x;
  });
})();
(function () {
  _a = "A";
  _b = "B";
  _4 = 42;
})();

斐波那契

(function () {
  function fibonacci(x) {
    return x <= 1 ? x : fibonacci(x - 1) + fibonacci(x - 2);
  }
  global.x = fibonacci(23);
})();
(function () {
  x = 28657;
})();

模块初始化

(function () {
  let moduleTable = {};
  function define(id, f) { moduleTable[id] = f; }
  function require(id) {
    let x = moduleTable[id];
    return x instanceof Function ? (moduleTable[id] = x()) : x;
  }
  global.require = require;
  define("one", function() { return 1; });
  define("two", function() { return require("one") + require("one"); });
  define("three", function() { return require("two") + require("one"); });
  define("four", function() { return require("three") + require("one"); });
})();
three = require("three");
(function () {
  function _2() {
    return 3 + 1;
  }

  var _1 = {
    one: 1,
    two: 2,
    three: 3,
    four: _2
  };

  function _0(id) {
    let x = _1[id];
    return x instanceof Function ? _1[id] = x() : x;
  }

  require = _0;
  three = 3;
})();

注意 大多数计算是如何预先初始化的。然而,计算四(_2)的函数保留在剩余程序中,因为它在初始化时没有被调用。

环境相互作用和分支

(function(){
  function fib(x) { return x <= 1 ? x : fib(x - 1) + fib(x - 2); }
  let x = Date.now();
  if (x === 0) x = fib(10);
  global.result = x;
})();
(function () {
  var _0 = Date.now();
  if (typeof _0 !== "number") {
    throw new Error("Prepack model invariant violation");
  }
  result = _0 === 0 ? 55 : _0;
})();

它是如何工作的?

几件事一同来实现 Prepack:

  • 抽象语法树(AST)

    Prepack在AST级别运行,使用 Babel 解析并生成JavaScript源代码。

  • 实际执行

    在Prepack的核心几乎是在JavaScript中实现兼容ECMAScript 5的解释器! 解析器严格遵循 ECMAScript 2016 语言规范, 重点是正确性和规范符合性。 您可以将Prepack中的解释器视为JavaScript的简洁实现。

    解释器能够跟踪和撤消所有的影响,包括所有的对象变化。 这样可以进行推测性的优化。

  • 符号执行

    除了对具体值进行计算外,Prepack的解释器还可以对通常来自环境相互作用的抽象值进行操作。例如,可以返回抽象值。您还可以通过辅助函数手动注入抽象值。例如Prepack跟踪通过抽象值执行的所有操作。当分支抽象值时,Prepack将执行fork并探索所有可能性。 因此,Prepack实现了JavaScript 的符号执行引擎。

  • 抽象解释

    当抽象值遇到分支时,符号执行将分支。 在控制流合并点,Prepack将加入分歧的执行,实现抽象解释。 连接变量和堆属性可能会导致条件抽象值。Prepack跟踪有关抽象值的值和类型域的信息。

  • 堆序列化

    在全局代码返回的初始化阶段结束时,Prepack捕获最终的堆。Prepack按顺序遍历堆,生成新的直接JavaScript代码,创建并链接初始化堆中可访问的所有对象。堆中的一些值可能是对抽象值进行计算的结果。对于这些值,Prepack会生成执行这些计算的代码,如原始程序所完成的那样。

环境很重要!

开箱即用,Prepack并没有完全模拟浏览器或node.js环境:Prepack没有内置的 document 或者 window。 实际上,当预编程引用这些属性的代码时,它们会评估为undefined。 您将必须在要使用Prepack进行处理的代码开头插入相关功能的模型。

以下 helper 函数有助于编写模型。

// Assume that a certain property has a simple known value.
__assumeDataProperty(global, "obscure", undefined);
// Assume that a certain property has a simple unknown value.
__assumeDataProperty(global, "notSoObscure", __abstract());
// Assume that a richly structured value exists
__assumeDataProperty(global, "rich", __abstract({
  x: __abstract("number"),
  y: __abstract("boolean"),
  z: __abstract("string"),
  nested: __abstract({
    x: __abstract()
  })
}));
// Forbid any accesses to an object except at known positions
__makePartial(global);
// At this point, accessing global.obscure, global.notSoObscure, global.rich.nested.x is okay,
// but accessing global.unknown or global.rich.unknown would cause an introspection error.

// The following tells Prepack to embed and call some code in the residual program.
// The code must not have any side effects on the reachable JavaScript heap.
__residual("object", function(delay) {
  return global.pushSelfDestructButton(delay);
}, "5 minutes");

路线图

短期

  • 稳定现有功能集,用于Prepack of React Native bundles
  • 与React Native工具链集成
  • 根据React Native使用的模块系统的假设构建优化

中期

  • 实施进一步的序列化优化,包括
    • 消除身份不暴露的对象,
    • 消除未使用的导出属性,
    • ...
  • Prepack 单个功能,基本块,语句,表达式
  • 全ES6一致性
  • 广泛支持模块系统
  • 假设ES6支持某些功能,延迟/忽略 Polyfills的应用
  • 实现进一步的兼容性目标,特别是web和node.js
  • 与JavaScript VM进行更深入的集成,以改进堆反序列化过程,包括
    • 暴露一个懒对象初始化概念 — 在第一次被创建的时刻,懒初始化一个对象,以一种不被JavaScript代码观察的方式
    • 通过专门的字节码对通用对象创建进行高效编码
    • 将代码分为两个阶段:1)非环境依赖阶段; VM可以安全地捕获并恢复生成的堆; 随后2)环境依赖阶段,通过对从环境获得的值执行任何残差计算来补丁堆
    • ...
  • 总结循环和递归

长期 — 利用Prepack作为平台

  • JavaScript Playground — 通过调整用JavaScript编写的JavaScript引擎来实验JavaScript功能,所有这些都是托管在浏览器中; 认为它是一个“Babel VM”,实现了不能仅仅被编译出来的新的JavaScript功能
  • 错误查找 - — 查找无条件崩溃,性能问题, ...
  • 效果分析器,例如检测模块工厂功能的可能副作用或强制执行纯净度注释
  • 类型分析
  • 信息流分析
  • 调用图表推理,允许内联和代码索引
  • 自动测试生成,利用符号执行功能与强类型的解器相结合来计算运行不同执行路径的输入
  • 智能模糊测试
  • JavaScript 沙盒 — 以不可观察的方式有效地测试JavaScript代码

相关技术

Closure Compiler 也优化JavaScript代码。 通过真正运行初始化阶段,展开循环和递归的全局代码,Prepack更进一步。Prepack专注于运行时性能,而Closure Compiler则强调JavaScript代码大小。