On May 4, Joel Marcey, a technical writer on Facebook’s open source team, posted a post on Hacker News titled “Prepack Helps Make JavaScript Code more efficient,” which got a lot of discussion in the community.
Officially billed as a tool for optimizing JavaScript source code, Prepack is actually a Partial Evaluator of JavaScript that performs compile-time calculations that would otherwise be performed at runtime. And rewrite JavaScript code to make it more efficient. Prepack effectively replaces the global code in the JavaScript code package with a simple assignment sequence, eliminating intermediate calculations and object allocation. For reinitialized code, Prepack can effectively cache the results of JavaScript parsing for optimal optimization.
Here are five concepts to help you better understand how Prepack works:
-
Abstract Syntax Tree (AST) Prepack runs at the AST level, parses and generates JavaScript source code using Babel.
-
At the heart of Prepack is a JavaScript interpreter that is almost fully compatible with ECMAScript 5 and closely conforms to the ECMAScript 2016 language specification, You can think of the interpreter in Prepack as being implemented entirely with reference to JavaScript. The interpreter can trace and undo the results of all object Mutation, enabling Speculative Optimization.
-
Symbolic Execution In addition to performing calculations on concrete values, Prepack’s interpreter can also operate on abstract values that are influenced by environmental interactions. For example date.now can return an abstract value, which you can manually inject with helper functions such as __abstract(). Prepack keeps track of all operations performed on abstract values, and when it encounters a branch, Prepack performs and explores all possibilities. So Prepack implements a set of JavaScript symbolic execution engines.
-
Abstract Interpretation notation Execution forks when it encounters branches of Abstract values, and Prepack implements the form of Abstract Interpretation by adding Diverged Execution at the control flow merge point. Concatenated variables and heap attributes may result in conditional abstract values, and Prepack keeps track of information about the abstract values and Type domains.
-
When the global code returns and the initialization phase is over, Prepack captures the final Heap and arranges the stacks in order, generating intuitive new JavaScript code that creates and initializes all accessible objects in the Heap. Some of the values in the heap may be calculations of abstract values for which Prepack will generate the code that the original program executed to complete the calculation.
The following is an example of an official Prepack optimization:
/* Hello World */ // Input (function () { function hello() { return 'hello'; } function world() { return 'world'; } global.s = hello() + ' ' + world(); }) (); // Output (function () { s = "hello world"; }) ();Copy the code
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; }); }) (); // Output (function () { _a = "A"; _b = "B"; _4 = 42; }) ();Copy the code
/* Fibonacci */ / Input (function () {function Fibonacci (x) {return x <= 1? x : fibonacci(x - 1) + fibonacci(x - 2); } global.x = fibonacci(23); }) (); // Output (function () { x = 28657; }) ();Copy the code
// / Input (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"); // Output (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; }) ();Copy the code
// / Input (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; }) (); // Output (function () { var _0 = Date.now(); if (typeof _0 ! == "number") { throw new Error("Prepack model invariant violation"); } result = _0 === 0 ? 55: _0; }) ();Copy the code
Here’s what the Prepack team has planned for the future:
-
Stabilize an existing feature set for pre-optimizing the React Native code package
-
Integrate with the React Native tool chain
-
Build optimizations based on the assumptions of the module system used by React Native
-
Further optimization of Serialization, including: eliminating objects that do not expose identities; Eliminate unused export attributes, and so on
-
Pre-optimize every function, base code block, statement, expression
-
Fully consistent with ES6
-
Support for a wide range of modular systems
-
Assume that ES6 supports some functionality, delays completion or simply ignores Polyfill applications
-
Further achieve compatibility goals in Web and Node.js environments
-
Deep integration with JavaScript virtual machines to improve the heap deserialization process, including: exposing the concept of “lazy object initialization” – initializing objects the first time they are used in a way that JavaScript is not aware of; Improve encoding efficiency of common object creation through specialized bytecode; The code is divided into two phases: 1) a non-environment dependent phase, where the virtual machine can safely capture and restore the generated heap; 2) The environment dependent phase, where all remaining calculations are performed to piece together the concrete heap by taking values from the environment, and so on
-
Summarize loops and recursion
-
JavaScript Playground – Experience JavaScript features by tweaking JavaScript engines written in JavaScript and hosted in the browser; You can think of it as a “Babel virtual machine” that implements new JavaScript features that can’t be compiled
-
Bug – Find abnormal crashes, execution problems…
-
Effect analysis, such as detecting possible side effects of module factory functions or enforcing pure comments
-
Types of analysis
-
Information flow analysis
-
Call graph reasoning, allowing inlining and code indexing
-
Automatic test generation that uses symbolic execution features in combination with Constraint Solver to calculate inputs to execute different execution paths
-
Smart Fuzzing
-
JavaScript sandbox – Effectively tests JavaScript code in an unobtainable way
Prepack is still in the early stages of development and is not yet ready for use in a production environment. The official recommendation is to try it out, and feedback is welcome to help fix the bugs.