Prepack, revived

A report from the Prepack revival · November 2026

Prepack, revived

Facebook set down its JavaScript partial evaluator in 2018, leaving behind problems its own authors called hard, undecidable and “a bridge too far.” We went back in with AI agents, an SMT solver and a fuzzer. This is what worked, what didn’t, and what it cost. Code: github.com/jlongster/prepack-revived Live demo: a React storefront rendered by the revived Prepack

40 of 194 pass

Prepack 0.2.54 (2018) · 43 silently wrong

P1-loops/collatz-data-dependent.js: Error or crashP1-loops/do-while-build-object.js: Silently wrongP1-loops/for-in-unknown-object.js: Error or crashP1-loops/gcd-while-two-vars.js: Error or crashP1-loops/issue-1174-global-unknown-bound.js: Error or crashP1-loops/issue-1174-unknown-bound-counter.js: Error or crashP1-loops/issue-1722-state-before-loop.js: Silently wrongP1-loops/issue-1959-shallow-equal.js: Error or crashP1-loops/issue-2053-babel-for-of.js: Error or crashP1-loops/issue-2055-while-string-search.js: Error or crashP1-loops/issue-2396-while-pure.js: Error or crashP1-loops/issue-2396-while-return.js: Error or crashP1-loops/issue-2477-babel-for-of-switch-return.js: Error or crashP1-loops/labeled-continue-nested.js: Error or crashP1-loops/loop-break-in-optimize.js: Error or crashP1-loops/loop-return-in-optimize.js: Error or crashP1-loops/loop-string-accumulator.js: Error or crashP1-loops/loop-throw-in-optimize.js: Error or crashP1-loops/loop-writes-known-object-unknown-keys.js: Error or crashP1-loops/nested-loops-unknown-bounds.js: Error or crashP1-loops/while-string-trim-global.js: Error or crashP2-recursion/concrete-recursion-under-abstract-cond.js: PassP2-recursion/deep-clone.js: Error or crashP2-recursion/descent-parser.js: Error or crashP2-recursion/factorial-under-condition.js: Error or crashP2-recursion/fib-optimized.js: Error or crashP2-recursion/flatten-recursive.js: Error or crashP2-recursion/gcd-recursive.js: Error or crashP2-recursion/issue-1586-fibonacci-abstract.js: Error or crashP2-recursion/issue-543-fib-date-now.js: PassP2-recursion/mutual-recursion-even-odd.js: Error or crashP2-recursion/recursive-path-lookup.js: Error or crashP2-recursion/tree-depth.js: Error or crashP2-recursion/tree-walk-sum.js: Error or crashP3-joins/catch-rethrow-abstract.js: Error or crashP3-joins/fuzz-nested-conditional-assign-return.js: PassP3-joins/fuzz-returns-counters.js: PassP3-joins/global-conditional-throw-then-work.js: Correct but too largeP3-joins/issue-1378-switch-abstract.js: PassP3-joins/issue-1378-switch-fallthrough.js: Error or crashP3-joins/issue-1869-duplicate-console-log.js: PassP3-joins/issue-2120-havoced-object-access-throws.js: Silently wrongP3-joins/issue-2120-try-catch-abstract-call.js: Error or crashP3-joins/issue-2354-multiple-returns.js: Silently wrongP3-joins/issue-2363-possibly-normal-completion.js: PassP3-joins/issue-2363-with-values.js: PassP3-joins/issue-2561-throw-before-constructor.js: Error or crashP3-joins/labeled-block-break-global.js: PassP3-joins/labeled-block-break.js: Silently wrongP3-joins/nested-returns-side-effects.js: Error or crashP3-joins/return-in-nested-function-conditional-objects.js: Silently wrongP3-joins/short-circuit-abstract-calls.js: PassP3-joins/switch-global-abstract.js: PassP3-joins/throw-in-constructor.js: Error or crashP3-joins/try-finally-return-abstract.js: Error or crashP4-paths/comparison-chain-numbers.js: Correct but too largeP4-paths/de-morgan-mixed-types.js: PassP4-paths/equality-refinement.js: PassP4-paths/infeasible-branch-pruning.js: Correct but too largeP4-paths/issue-2327-simplify-binary.js: PassP4-paths/issue-2355-truthiness-return.js: Silently wrongP4-paths/issue-2361-pushing-false.js: PassP4-paths/issue-2563-undefined-not-null.js: PassP4-paths/issue-2564-null-not-undefined.js: PassP4-paths/nested-ternary-falsy-values.js: Error or crashP4-paths/null-check-then-property.js: PassP4-paths/repeated-condition-reuse.js: Error or crashP4-paths/truthiness-ternary-mixed.js: Error or crashP4-paths/typeof-refinement.js: Correct but too largeP5-ranges/clamp-then-loop.js: Error or crashP5-ranges/conditional-index-write.js: Error or crashP5-ranges/conditional-pop-length-arith.js: Error or crashP5-ranges/conditional-push-global-many-unique-names.js: Error or crashP5-ranges/conditional-push-global-many.js: Error or crashP5-ranges/conditional-push-last-element.js: Error or crashP5-ranges/conditional-push-optimized.js: Error or crashP5-ranges/issue-1066-conditional-push.js: Error or crashP5-ranges/masked-index-lookup.js: Correct but too largeP5-ranges/string-length-range.js: PassP6-havoc/abstract-fn-leaks-global-object.js: Error or crashP6-havoc/aliasing-two-params.js: PassP6-havoc/array-filter-callback-mutates-local.js: PassP6-havoc/getter-on-abstract-object.js: PassP6-havoc/issue-1750-lazy-cache.js: Silently wrongP6-havoc/issue-1750-lazy-singleton.js: Silently wrongP6-havoc/issue-2011-leaked-binding.js: PassP6-havoc/issue-2012-havoc-binding-capture.js: Correct but too largeP6-havoc/issue-2179-topval-alias.js: PassP6-havoc/issue-2226-setter-on-prototype.js: Correct but too largeP6-havoc/issue-2319-indexof.js: PassP6-havoc/issue-2319-json-stringify-local.js: Error or crashP6-havoc/issue-2319-math-abs.js: Error or crashP6-havoc/issue-2319-parseint.js: Error or crashP6-havoc/issue-2319-regexp-test.js: Error or crashP6-havoc/issue-977-read-write-conflict.js: PassP6-havoc/object-keys-loop-over-param.js: Error or crashP6-havoc/object-passed-to-unknown-fn.js: PassP6-havoc/unknown-fn-mutates-captured-binding.js: PassP7-environment/date-local-timezone.js: Silently wrongP7-environment/date-now-typeof.js: Error or crashP7-environment/dom-build-tree-at-init.js: Error or crashP7-environment/dom-get-element-by-id.js: Error or crashP7-environment/guarded-speculation-fallback.js: Silently wrongP7-environment/guarded-speculation-holds.js: Silently wrongP7-environment/intl-number-format.js: Silently wrongP7-environment/local-storage-init.js: Silently wrongP7-environment/math-random-init.js: Error or crashP7-environment/navigator-user-agent.js: Silently wrongP7-environment/node-require-module.js: Silently wrongP7-environment/number-to-locale-string.js: Error or crashP7-environment/process-env-branch.js: Silently wrongP7-environment/react-dom-vendor-prefix-init.js: Error or crashP7-environment/request-animation-frame.js: Silently wrongP7-environment/timers-set-interval-count.js: PassP7-environment/timers-set-timeout-order.js: PassP7-environment/umd-three-way-probe.js: Silently wrongP7-environment/window-global-alias.js: Silently wrongP8-size/crc32-table.js: Correct but too largeP8-size/issue-2011-binding-writes.js: Correct but too largeP8-size/issue-2140-inlining-explosion.js: Error or crashP8-size/issue-2220-empty-if.js: Correct but too largeP8-size/issue-2286-cse-effectful.js: Correct but too largeP8-size/repeated-defaults-objects.js: Correct but too largeP8-size/residual-closure-drags-code.js: PassP8-size/string-builder-unrolled.js: Correct but too largeP8-size/unrolled-table-1000.js: Correct but too largeP9-generators-async/async-function-defined-at-init.js: Silently wrongP9-generators-async/generator-function-defined-not-called.js: Silently wrongP9-generators-async/generator-suspended-at-init.js: Error or crashP9-generators-async/promise-then-at-init.js: Silently wrongfuzz/array-abstract-index-set-length.js: Silently wrongfuzz/call-after-nested-returns.js: Silently wrongfuzz/composed-saved-completion-labeled-break.js: Silently wrongfuzz/conditional-call-base-nullish.js: Silently wrongfuzz/conditional-inner-throw-outer-alternate.js: Silently wrongfuzz/conditional-nested-temporal-scope.js: Error or crashfuzz/conditional-null-check-negation.js: Silently wrongfuzz/conditional-push-while-join-mismatch.js: Error or crashfuzz/conditional-unknown-write-empty-to-undefined.js: Silently wrongfuzz/dead-if-hoisted-var.js: Silently wrongfuzz/delete-abstract-coercible-base.js: Error or crashfuzz/do-while-break-abrupt-widen.js: Error or crashfuzz/do-while-emitter-active-generator.js: Error or crashfuzz/do-while-false-abrupt.js: Silently wrongfuzz/do-while-object-emitter.js: Error or crashfuzz/do-while-object-hoisted-param.js: Silently wrongfuzz/for-in-continue-return-join.js: Silently wrongfuzz/infeasible-path-explored-crash.js: Error or crashfuzz/inlining-probe-nullish-arg.js: Error or crashfuzz/labeled-break-before-throw-call.js: Error or crashfuzz/labeled-break-skips-code-after-block.js: Silently wrongfuzz/leaked-binding-declared-across-passes.js: Error or crashfuzz/leaked-object-in-operator.js: Error or crashfuzz/loop-null-check-path-condition.js: Error or crashfuzz/map-cases-path-condition-leak.js: Error or crashfuzz/merged-join-uses-earlier-declared-value.js: Error or crashfuzz/nested-try-leaked-object-mutation.js: Error or crashfuzz/not-nullish-falsy-logical.js: Silently wrongfuzz/path-implies-both-true-visitor.js: Error or crashfuzz/primitive-receiver-strict-set.js: Silently wrongfuzz/pure-unary-coercion-throws.js: Error or crashfuzz/residual-loop-closure-mutated-param.js: Silently wrongfuzz/switch-empty-nan-discriminant.js: Error or crashfuzz/switch-hoisted-var-loop.js: Error or crashfuzz/switch-nested-abrupt-default.js: Error or crashfuzz/throw-before-try-catch-finally.js: Silently wrongfuzz/throw-in-call-argument-join.js: Error or crashfuzz/unknown-key-write-property-order.js: Error or crashfuzz/unknown-property-concrete-get-partial.js: Error or crashfuzz/unknown-property-concrete-get.js: Error or crashfuzz/unused-in-non-object-throws.js: Silently wrongfuzz/unused-property-read-throws.js: Silently wrongfuzz/update-expression-string.js: Silently wrongfuzz/widen-nested-loop-in-helper.js: Error or crashreal-world/babel-class-hierarchy.js: Passreal-world/config-parser-concrete.js: Passreal-world/config-parser-env-override.js: Error or crashreal-world/event-emitter-init.js: Passreal-world/i18n-plural-rules.js: Passreal-world/lodash-init.js: Passreal-world/lookup-table-char-classes.js: Error or crashreal-world/module-system.js: Correct but too largereal-world/react-like-render.js: Error or crashreal-world/template-compiler.js: Passreal-world/template-render-optimized.js: Passreal-world/url-router.js: Error or crashreal-world-libs/crc32-sequential-loop-leaked-typedarray.js: Passreal-world-libs/d3-internmap-class-extends-map.js: Error or crashreal-world-libs/d3-scale-default-param-factory.js: Error or crashreal-world-libs/handlebars-globalthis-definegetter.js: Silently wrongreal-world-libs/marked-class-static-getter.js: Error or crashreal-world-libs/marked-optional-catch-binding.js: Passreal-world-libs/redux-random-split-join.js: Error or crashreal-world-libs/underscore-document-childnodes.js: Error or crash

190 of 194 pass

Revived Prepack (2026) · 0 silently wrong

P1-loops/collatz-data-dependent.js: PassP1-loops/do-while-build-object.js: PassP1-loops/for-in-unknown-object.js: PassP1-loops/gcd-while-two-vars.js: PassP1-loops/issue-1174-global-unknown-bound.js: PassP1-loops/issue-1174-unknown-bound-counter.js: PassP1-loops/issue-1722-state-before-loop.js: PassP1-loops/issue-1959-shallow-equal.js: PassP1-loops/issue-2053-babel-for-of.js: PassP1-loops/issue-2055-while-string-search.js: PassP1-loops/issue-2396-while-pure.js: PassP1-loops/issue-2396-while-return.js: PassP1-loops/issue-2477-babel-for-of-switch-return.js: PassP1-loops/labeled-continue-nested.js: PassP1-loops/loop-break-in-optimize.js: PassP1-loops/loop-return-in-optimize.js: PassP1-loops/loop-string-accumulator.js: PassP1-loops/loop-throw-in-optimize.js: PassP1-loops/loop-writes-known-object-unknown-keys.js: PassP1-loops/nested-loops-unknown-bounds.js: PassP1-loops/while-string-trim-global.js: PassP2-recursion/concrete-recursion-under-abstract-cond.js: PassP2-recursion/deep-clone.js: PassP2-recursion/descent-parser.js: PassP2-recursion/factorial-under-condition.js: PassP2-recursion/fib-optimized.js: PassP2-recursion/flatten-recursive.js: PassP2-recursion/gcd-recursive.js: PassP2-recursion/issue-1586-fibonacci-abstract.js: PassP2-recursion/issue-543-fib-date-now.js: PassP2-recursion/mutual-recursion-even-odd.js: PassP2-recursion/recursive-path-lookup.js: PassP2-recursion/tree-depth.js: PassP2-recursion/tree-walk-sum.js: PassP3-joins/catch-rethrow-abstract.js: PassP3-joins/fuzz-nested-conditional-assign-return.js: PassP3-joins/fuzz-returns-counters.js: PassP3-joins/global-conditional-throw-then-work.js: PassP3-joins/issue-1378-switch-abstract.js: PassP3-joins/issue-1378-switch-fallthrough.js: PassP3-joins/issue-1869-duplicate-console-log.js: PassP3-joins/issue-2120-havoced-object-access-throws.js: PassP3-joins/issue-2120-try-catch-abstract-call.js: PassP3-joins/issue-2354-multiple-returns.js: PassP3-joins/issue-2363-possibly-normal-completion.js: PassP3-joins/issue-2363-with-values.js: PassP3-joins/issue-2561-throw-before-constructor.js: PassP3-joins/labeled-block-break-global.js: PassP3-joins/labeled-block-break.js: PassP3-joins/nested-returns-side-effects.js: PassP3-joins/return-in-nested-function-conditional-objects.js: PassP3-joins/short-circuit-abstract-calls.js: PassP3-joins/switch-global-abstract.js: PassP3-joins/throw-in-constructor.js: PassP3-joins/try-finally-return-abstract.js: PassP4-paths/comparison-chain-numbers.js: PassP4-paths/de-morgan-mixed-types.js: PassP4-paths/equality-refinement.js: PassP4-paths/infeasible-branch-pruning.js: PassP4-paths/issue-2327-simplify-binary.js: PassP4-paths/issue-2355-truthiness-return.js: PassP4-paths/issue-2361-pushing-false.js: PassP4-paths/issue-2563-undefined-not-null.js: PassP4-paths/issue-2564-null-not-undefined.js: PassP4-paths/nested-ternary-falsy-values.js: PassP4-paths/null-check-then-property.js: PassP4-paths/repeated-condition-reuse.js: PassP4-paths/truthiness-ternary-mixed.js: PassP4-paths/typeof-refinement.js: PassP5-ranges/clamp-then-loop.js: PassP5-ranges/conditional-index-write.js: PassP5-ranges/conditional-pop-length-arith.js: PassP5-ranges/conditional-push-global-many-unique-names.js: PassP5-ranges/conditional-push-global-many.js: Error or crashP5-ranges/conditional-push-last-element.js: PassP5-ranges/conditional-push-optimized.js: PassP5-ranges/issue-1066-conditional-push.js: PassP5-ranges/masked-index-lookup.js: PassP5-ranges/string-length-range.js: PassP6-havoc/abstract-fn-leaks-global-object.js: Error or crashP6-havoc/aliasing-two-params.js: PassP6-havoc/array-filter-callback-mutates-local.js: PassP6-havoc/getter-on-abstract-object.js: PassP6-havoc/issue-1750-lazy-cache.js: PassP6-havoc/issue-1750-lazy-singleton.js: PassP6-havoc/issue-2011-leaked-binding.js: PassP6-havoc/issue-2012-havoc-binding-capture.js: PassP6-havoc/issue-2179-topval-alias.js: PassP6-havoc/issue-2226-setter-on-prototype.js: PassP6-havoc/issue-2319-indexof.js: PassP6-havoc/issue-2319-json-stringify-local.js: PassP6-havoc/issue-2319-math-abs.js: PassP6-havoc/issue-2319-parseint.js: PassP6-havoc/issue-2319-regexp-test.js: PassP6-havoc/issue-977-read-write-conflict.js: PassP6-havoc/object-keys-loop-over-param.js: PassP6-havoc/object-passed-to-unknown-fn.js: PassP6-havoc/unknown-fn-mutates-captured-binding.js: PassP7-environment/date-local-timezone.js: PassP7-environment/date-now-typeof.js: PassP7-environment/dom-build-tree-at-init.js: PassP7-environment/dom-get-element-by-id.js: PassP7-environment/guarded-speculation-fallback.js: PassP7-environment/guarded-speculation-holds.js: PassP7-environment/intl-number-format.js: PassP7-environment/local-storage-init.js: PassP7-environment/math-random-init.js: PassP7-environment/navigator-user-agent.js: PassP7-environment/node-require-module.js: PassP7-environment/number-to-locale-string.js: PassP7-environment/process-env-branch.js: PassP7-environment/react-dom-vendor-prefix-init.js: PassP7-environment/request-animation-frame.js: PassP7-environment/timers-set-interval-count.js: PassP7-environment/timers-set-timeout-order.js: PassP7-environment/umd-three-way-probe.js: PassP7-environment/window-global-alias.js: PassP8-size/crc32-table.js: PassP8-size/issue-2011-binding-writes.js: PassP8-size/issue-2140-inlining-explosion.js: PassP8-size/issue-2220-empty-if.js: PassP8-size/issue-2286-cse-effectful.js: PassP8-size/repeated-defaults-objects.js: PassP8-size/residual-closure-drags-code.js: PassP8-size/string-builder-unrolled.js: PassP8-size/unrolled-table-1000.js: PassP9-generators-async/async-function-defined-at-init.js: PassP9-generators-async/generator-function-defined-not-called.js: PassP9-generators-async/generator-suspended-at-init.js: Error or crashP9-generators-async/promise-then-at-init.js: Passfuzz/array-abstract-index-set-length.js: Passfuzz/call-after-nested-returns.js: Passfuzz/composed-saved-completion-labeled-break.js: Passfuzz/conditional-call-base-nullish.js: Passfuzz/conditional-inner-throw-outer-alternate.js: Passfuzz/conditional-nested-temporal-scope.js: Passfuzz/conditional-null-check-negation.js: Passfuzz/conditional-push-while-join-mismatch.js: Passfuzz/conditional-unknown-write-empty-to-undefined.js: Passfuzz/dead-if-hoisted-var.js: Passfuzz/delete-abstract-coercible-base.js: Passfuzz/do-while-break-abrupt-widen.js: Passfuzz/do-while-emitter-active-generator.js: Passfuzz/do-while-false-abrupt.js: Passfuzz/do-while-object-emitter.js: Passfuzz/do-while-object-hoisted-param.js: Passfuzz/for-in-continue-return-join.js: Passfuzz/infeasible-path-explored-crash.js: Passfuzz/inlining-probe-nullish-arg.js: Passfuzz/labeled-break-before-throw-call.js: Error or crashfuzz/labeled-break-skips-code-after-block.js: Passfuzz/leaked-binding-declared-across-passes.js: Passfuzz/leaked-object-in-operator.js: Passfuzz/loop-null-check-path-condition.js: Passfuzz/map-cases-path-condition-leak.js: Passfuzz/merged-join-uses-earlier-declared-value.js: Passfuzz/nested-try-leaked-object-mutation.js: Passfuzz/not-nullish-falsy-logical.js: Passfuzz/path-implies-both-true-visitor.js: Passfuzz/primitive-receiver-strict-set.js: Passfuzz/pure-unary-coercion-throws.js: Passfuzz/residual-loop-closure-mutated-param.js: Passfuzz/switch-empty-nan-discriminant.js: Passfuzz/switch-hoisted-var-loop.js: Passfuzz/switch-nested-abrupt-default.js: Passfuzz/throw-before-try-catch-finally.js: Passfuzz/throw-in-call-argument-join.js: Passfuzz/unknown-key-write-property-order.js: Passfuzz/unknown-property-concrete-get-partial.js: Passfuzz/unknown-property-concrete-get.js: Passfuzz/unused-in-non-object-throws.js: Passfuzz/unused-property-read-throws.js: Passfuzz/update-expression-string.js: Passfuzz/widen-nested-loop-in-helper.js: Passreal-world/babel-class-hierarchy.js: Passreal-world/config-parser-concrete.js: Passreal-world/config-parser-env-override.js: Passreal-world/event-emitter-init.js: Passreal-world/i18n-plural-rules.js: Passreal-world/lodash-init.js: Passreal-world/lookup-table-char-classes.js: Passreal-world/module-system.js: Passreal-world/react-like-render.js: Passreal-world/template-compiler.js: Passreal-world/template-render-optimized.js: Passreal-world/url-router.js: Passreal-world-libs/crc32-sequential-loop-leaked-typedarray.js: Passreal-world-libs/d3-internmap-class-extends-map.js: Passreal-world-libs/d3-scale-default-param-factory.js: Passreal-world-libs/handlebars-globalthis-definegetter.js: Passreal-world-libs/marked-class-static-getter.js: Passreal-world-libs/marked-optional-catch-binding.js: Passreal-world-libs/redux-random-split-join.js: Passreal-world-libs/underscore-document-childnodes.js: Pass
  • Pass
  • Correct but too large
  • Error or crash
  • Silently wrong
Each square is one program from the hard-case corpus: a problem from Prepack’s issue tracker, a bug found by fuzzing, or a piece of a real library. Squares are in the same order in both grids.

Part 1

Prepack and its eight hard problems

A partial evaluator for JavaScript, and the problems its authors left open, in their own words.

Prepack is Facebook’s partial evaluator for JavaScript. It runs the code a bundle would run when it loads, using its own interpreter, and writes out a new bundle that rebuilds the result directly. Whatever it can’t know at build time (the clock, the user, the DOM) it tracks as an abstract value and leaves as residual code that runs later. Facebook set it down at the end of 2018; asked in 2020 whether it was dead, its lead answered that “no one currently at Facebook seems to have a scenario in mind where they believe that Prepack can be of use.”

Partial evaluation in one example: the loop runs at build time, so the output contains only the finished table; Date.now() can’t be known until the app runs, so it stays in the output as code. Every output on this page is the verbatim output of the tool named in its header, produced by site/scripts/run-examples.js; the table under each comparison runs the original program and each output and compares the results.
Inputintro-partial.js
// Build a lookup table when the app starts.
const units = ["B", "KB", "MB", "GB", "TB"];
const bytesPer = {};
for (let i = 0; i < units.length; i++) {
  bytesPer[units[i]] = Math.pow(1024, i);
}

// Only known when the app actually runs.
const startedAt = Date.now();

global.app = { bytesPer, startedAt };
Prepack 0.2.54the last release, 2018
Succeeds: same result as the original
(function () {
  var _$1 = this;
  var _$0 = _$1.Date.now();
  var _1 = {
    B: 1,
    KB: 1024,
    MB: 1048576,
    GB: 1073741824,
    TB: 1099511627776
  };
  var _0 = {
    bytesPer: _1,
    startedAt: _$0
  };
  _$1.app = _0;
}).call(this);
16 lines · 248 bytes · exit 0 · 0.3 s
RunOriginal programPrepack 0.2.54 output
app.bytesPer.TB, typeof app.startedAt1099511627776,number1099511627776,number

Prepack also has a second mode: __optimize(fn) specializes a function for abstract arguments, running its body with every parameter unknown. Most examples below use it, because it is where the hard problems show up fastest.

Eight hard problems

The code and the issue tracker document the technical problems the original team never solved. We grouped them into eight. Here they are in the developers’ own words.

P1 Loops with unknown trip counts

Prepack could only unroll loops. When the bound was unknown it gave up, or after six speculative iterations failed. #1174 (“Compute fixed points for loops”) was opened in 2017 and never closed.

Prepack gives up when hitting the loop with the abstract condition. This is a hard problem.

Nikolai Tillmann, Prepack engineer · #2055, June 1, 2018

Yes, with loops you have to widen and once you widen, precision is lost. Range domains let you regain some of this precision. If you range over an unbounded collection of strings and use them as property names, it's pretty much game over.

Herman Venter, Prepack lead · #1722, April 9, 2018

P2 Recursion under unknown conditions

Recursion was unrolled to the maximum stack depth. The summarization that should have stopped it was switched off (#1586). The most-upvoted bug in the tracker, #543 with 121 reactions, is a one-line change that turned a recursive call into 1,065 lines of output.

In general, the question whether a function call is going to result in unbounded recursion is undecidable. The check we have currently is known to be conceptually too conservative --- it just looks whether a recursive call is guarded by any abstract condition.

Nikolai Tillmann, Prepack engineer · #1586, March 14, 2018

Prepack cannot just skip a loop (or recursive call), as Prepack then wouldn't know what all possible side effects on the heap would, and what values the code could return or exceptions it could throw. That's a hard problem, and it's on our longer term roadmap.

Nikolai Tillmann, Prepack engineer · #520, May 8, 2017

P3 Joining control flow

return, throw and break under unknown conditions; switch on unknown values (labeled “level 4 (hard)” in #1378); try/catch around code that might throw. The fuzzer the team built in 2018 found silent miscompiles here (#2354, #2355).

Prepack outputs invalid code without a diagnostic.

Caleb Meredith, author of Prepack’s fuzzer · #2354, August 1, 2018

P4 Reasoning about path conditions

Facts like “we are inside if (x != null)” were tracked by a hand-written simplifier and implication engine, with arbitrary recursion caps. #2355: if (a) return true; return false; was simplified to return a. The serializer carries a TODO that admits “something else going on that is not yet understood”.

P5 Numeric facts

After if (x) a.push(42), the length of a is “0 or 1”. Prepack couldn’t represent that, so the next push failed (#1066).

I'm not sure we are ready to head down this slippery slope of reasoning over abstract arithmetic.

Herman Venter, Prepack lead · #580, May 5, 2017

P6 Objects escaping to unknown code

Anything passed to code Prepack couldn’t see had to be assumed modified (“havoced”), including arguments to harmless built-ins: #2319 counted RegExp.prototype.test, indexOf, parseInt and Math.abs as the top bail-outs in Facebook’s bundle. Caches that an optimized function fills lazily (#1750) needed what Venter called the outer fixed point:

This is indeed kind of like a loop. In the static analysis world, this is know as computing the outer fixed point. Doing it properly is a bridge too far for us right now, so some hackery will be require in the meanwhile.

Herman Venter, Prepack lead · #1750, April 17, 2018

P7 Modeling the environment

Prepack had to know what window, document, timers and time zones do. #24 (“Add DOM shims”, 76 reactions) predates the launch and stayed open; the models that existed were thousands of lines, written by hand.

Getting to the point where it can usefully run on real browser based applications is going to take a lot of very hard work and a long time.

Herman Venter, Prepack lead · #24 “Add DOM shims”, July 29, 2018

P8 Output size

The flip side of evaluating everything: unrolled loops, materialized tables, duplicated helpers. The published 0.2.54 makes lodash about 3× larger after minification, 2× larger gzipped, and slower to load on a modern V8. Sebastian Markbåge (gist) named output size as “ultimately the reason we abandoned that particular approach.”

One compiler bailout, and the difference in the bundle is huge.

Dan Abramov, React team · Hacker News, December 21, 2020

Modern language features sat outside the list because they are “only” spec work, but one of them touches the core of abstract interpretation. The generators discussion in #712 stalled on suspending a computation in the middle of exploring both sides of an unknown branch:

More importantly, how do you iterate an if statement where the condition is abstract?

Herman Venter, Prepack lead · #712, June 10, 2017

Part 2 takes the problems one at a time: how the revived Prepack approaches each one, what is still out of reach, and the original and revived outputs side by side. We also explored a different architecture that sidesteps the interpreter entirely, running initialization in the real engine and emitting the heap it builds; that is written up separately.

Part 2

Reviving Prepack

Problem by problem: the technique, the key insight, what is still out of reach, and the original and revived outputs side by side.

Each problem below gets the same treatment: what the original developers said about it, the technique the revived Prepack uses, what is still out of reach, and a real before/after. One rule holds throughout: when Prepack can’t show that a transformation is right, it stops with a diagnostic instead of emitting something that might be wrong. First, the overall result.

The hard-case corpus

To measure the problems rather than argue about them, we turned the issue tracker into a corpus of 194 small, deterministic programs: the repros from the issues above, variants of them, minimized bugs found by a differential fuzzer, and excerpts of real libraries. Each program names its source issue and its goal in a header, and runs unchanged in plain Node, so every case can be checked by running the original and Prepack’s output side by side.

On the 194 cases, the published Prepack 0.2.54 passes 40, crashes or reports an error on 96, and is silently wrong on 43. The revived build passes 190, with 0 wrong and 4 clean errors. Prepack’s own regression suite passes 3,744 of 3,744 runs (the original has 3,378), and across 22 real-world libraries and six option profiles each, the revived build produces no wrong output.

Corpus folder0.2.54 passeswrongRevived passeswrong
P1 Loops0/21221/210
P2 Recursion2/13013/130
P3 Control-flow joins9/21421/210
P4 Path conditions7/14114/140
P5 Numeric facts1/1009/100
P6 Havoc and built-ins9/19218/190
P7 Environment2/191119/190
P8 Output size1/909/90
P9 Generators and async0/433/40
Fuzzer-found bugs0/441943/440
Real-world init snippets7/12012/120
Real library excerpts2/818/80
All 194 cases40/19443190/1940

The four remaining errors are deliberate: a test that checks the duplicate-name diagnostic, an unfrozen object leaking from global code (reported, as the original test suite expects), a generator left suspended at the end of initialization (see modern JavaScript), and one fuzzer case that bails out cleanly with correct recovered output.

P1 · Loops

If the behavior of a loop can be nicely summarized as a set of unknown properties on known objects, the the effects of the loop on the heap is known and it will be safe to emit a residual loop into the code at the point of time associated with the loop.

Herman Venter, Prepack lead · #1174 “Compute fixed points for loops”, November 18, 2017 (still open)

That was the plan in 2017, and it is what the revived Prepack does, in residualLoop.js. It still tries to unroll a loop first. When that fails it rewrites the loop into a small closure, passes in the values the loop only reads, marks the objects it writes as escaped, and emits the loop as residual code at the right point in time. Before accepting the residual loop it evaluates one symbolic iteration against that state and checks every effect: if the body could touch anything that wasn’t accounted for, Prepack refuses with a diagnostic instead of guessing. The first case is #1174 itself:

A loop with an unknown bound in an optimized function. 0.2.54 exits with an error that has no message (the symptom reported in #2396), though the code it prints happens to work. The revived build emits the residual loop cleanly.
Inputloops-1174.js
function sum(n) {
  var s = 0;
  for (var i = 0; i < n; i++) {
    s += i;
  }
  return s;
}
if (global.__optimize) __optimize(sum);
Prepack 0.2.54the last release, 2018
Reports an error (exit 1), then prints its fallback
var sum;
(function () {
  var _$3 = this;
  var _1 = function (n) {
    var __get_scope_binding_0 = function (__selector) {
      var __captured;
      switch (__selector) {
        case 0:
          __captured = [void 0];
          break;
      }
      __scope_0[__selector] = __captured;
      return __captured;
    };
    var __scope_0 = new Array(1);
    var __leaked_0, __leaked_1;
    var _5 = function () {
      var __captured__scope_1 = __scope_0[0] || __get_scope_binding_0(0);
      for (__captured__scope_1[0] = 0; __captured__scope_1[0] < __leaked_0; __captured__scope_1[0]++) {
        __leaked_1 += __captured__scope_1[0];
      }
    };
    __leaked_0 = n;
    __leaked_1 = 0;
    var _$1 = _5();
    var _$2 = __leaked_1;
    return _$2;
  };
  _$3.sum = _1;
}).call(this);
stderr
In input file loops-1174.js(3:23) FatalError PP0001:  (https://github.com/facebook/prepack/wiki/PP0001)
    at sum (loops-1174.js:3:23)
Prepack failed, reporting 1 fatal error.
30 lines · 792 bytes · exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var sum;
(function () {
  var _$7 = this;

  _$7.sum = function (n) {
    var __l0, __l1, __l2, __l3;

    var _6 = function (n) {
      for (; __l2 < n; __l2++) {
        __l3 += __l2;
      }
    };

    __l2 = 0;
    __l3 = 0;

    _6(n);

    var _$5 = __l2;
    var _$6 = __l3;
    return _$6;
  };
}).call(this);
23 lines · 319 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
sum(0), sum(5), sum(10)0,10,450,10,450,10,45

#1722 is the case Venter answered with “game over.” State built before the loop is lost when the loop is widened. In 0.2.54 the output is not just imprecise, it is wrong: the first two keys disappear and the rest shift by one.

#1722, with the unknown bound named n0. The revived build keeps the pre-loop state and emits the loop itself.
Inputloops-1722.js
var n = global.__abstract ? __abstract("number", "n0") : n0;
var arr = { 0: "a", 1: "b" };
var i = 2;
do {
  arr[i] = "x";
  i++;
} while (i < n);
var result = arr;
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
var result, i, arr, n;
(function () {
  var _$2 = this;
  var __empty = {};
  var _0 = n0;
  _$2.n = _0;
  var _2 = {};
  _$2.arr = _2;
  _$2.i = 2;
  var _J = _$2;
  _J.i = 2;
  do {
    var _7 = 1 + _J.i;
    _$2.i = _7;
    var _$0 = _7;
    var _C = "" + _J.i;
    _2[_C] = "x";
    _J.i = _$0;
    var _H = _7 < _0;
    var _$1 = _H;
  } while (_$1);
  _$2.result = _2;
}).call(this);
23 lines · 390 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var result, i, arr, n;
(function () {
  var _$3 = this;

  _$3.n = n0;
  var _2 = {};
  _$3.arr = _2;
  _$3.i = 2;
  _2[0] = "a";
  _2[1] = "b";
  delete _2[2];
  delete _2[3];

  (function () {
    do {
      arr[i] = "x";
      i++;
    } while (i < n);
  })();

  _$3.result = _2;
}).call(this);
22 lines · 299 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
n0 = 5{"0":"a","1":"b","2":"x","3":"x","4":"x"} i=5{"3":"x","4":"x","5":"x"} i=5 ✗ differs{"0":"a","1":"b","2":"x","3":"x","4":"x"} i=5

And #2055, Nikolai Tillmann’s “hard problem”: a while loop scanning a string whose contents are unknown.

#2055. The revived build passes the string into the loop closure as a read-only parameter; only the counter is shared.
Inputloops-2055.js
function fn(props, splitPoint) {
  var text = props.text || "";
  text = text.replace(/\s*$/, "");
  if (splitPoint !== null) {
    while (text[splitPoint - 1] === "\n") {
      splitPoint--;
    }
  }
  return splitPoint;
}
if (global.__optimize) __optimize(fn);
Prepack 0.2.54the last release, 2018
Reports an error (exit 1), then prints its fallback
var fn;
(function () {
  var _$8 = this;
  var _1 = function (props, splitPoint) {
    var __leaked_0, __leaked_1;
    var _C = function () {
      for (; __leaked_1[__leaked_0 - 1] === "\n";) {
        __leaked_0--;
      }
    };
    var _$0 = props.text;
    if (_$0) {
      var _3 = /\s*$/;
      var _$2 = _$0.replace(_3, "");
    }
    var _5 = splitPoint !== null;
    var _8 = _$0 ? _$2 : "";
    if (_5) {
      __leaked_0 = splitPoint;
      __leaked_1 = _8;
      var _$6 = _C();
    } else {
      __leaked_0 = splitPoint;
      __leaked_1 = _8;
    }
    var _$7 = __leaked_0;
    return _$7;
  };
  _$8.fn = _1;
}).call(this);
stderr
In input file loops-2055.js(5:37) FatalError PP0001:  (https://github.com/facebook/prepack/wiki/PP0001)
    at fn (loops-2055.js:5:37)
Prepack failed, reporting 1 fatal error.
30 lines · 642 bytes · exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var fn;
(function () {
  var _$D = this;

  _$D.fn = function (props, splitPoint) {
    var __l0, __l1;

    var _G = function (text) {
      for (; text[__l1 - 1] === "\n";) {
        __l1--;
      }
    };

    var _$0 = props.text;
    var _$1 = props.text;

    if (_$0) {
      var _$2 = _$0.replace(/\s*$/, "");
    }

    var _8 = splitPoint !== null;

    if (_8) {
      __l1 = splitPoint;

      _G(_$0 ? _$2 : "");

      var _$C = __l1;
    }

    return _8 ? _$C : splitPoint;
  };
}).call(this);
33 lines · 510 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
fn({text:"ab\n\ncd"}, 4), fn({}, null), fn({text:"a\n\n\nb"}, 4)[ 2, null, 1 ][ 2, null, 1 ][ 2, null, 1 ]

All 21 loop cases pass, including Babel-lowered for-of loops with large switch bodies (#2053, #2477), labeled break/continue, and return or throw inside loops. What stays out is honest: a loop inside an optimized function that mutates global state, or a global-code loop that calls unknown functions, is refused with a clear diagnostic, because summarizing arbitrary heap effects over an unknown number of iterations needs whole-program reasoning. That is the part of the problem that is undecidable in general, and the residual loop sidesteps it only when the effects are local.

P2 · Recursion

TODO #1586: abstractRecursionSummarization is disabled for now, as it is likely too limiting (as observed in large internal tests).

Prepack source · src/methods/call.js, line 400

The disabled code had five structural bugs (it always aborted at the end anyway). It is replaced by recursion.js, built on one insight: recursion that stays concrete keeps unrolling, but as soon as a recursive call is reached under a new abstract condition, Prepack stops and emits a call to the original function. In optimized functions it first checks that the cluster of functions involved only touches state it can account for; in global code it snapshots everything the function reads and proves that nothing changes it afterwards.

fib called with an unknown argument. 0.2.54 unrolls the recursion without bound; the run was killed after 60 seconds with no output. The revived build emits a residual call.
Inputrecursion-fib.js
function fib(n) {
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
function f(n) {
  return "fib=" + fib(n);
}
if (global.__optimize) __optimize(f);
Prepack 0.2.54the last release, 2018
No output: killed after 60 s, still unrolling
No output
exit killed · 60.3 s
Revived Prepackthis project
Succeeds: same result as the original
var fib, f;
(function () {
  var _$K = this;

  var _3 = function (n) {
    var _$I = _2(n);

    var _$J = "fib=" + _$I;

    return _$J;
  };

  var _2 = function (n) {
    return n <= 1 ? n : fib(n - 1) + fib(n - 2);
  };

  _$K.fib = _2;
  _$K.f = _3;
}).call(this);
19 lines · 271 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
f(7), f(12)fib=13,fib=144no programfib=13,fib=144
Global code: 0.2.54 unrolls fact to the maximum stack depth and stops with PP0045.
Inputrecursion-fact.js
var k = global.__abstract ? __abstract("number", "k0") : k0;
function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);
}
var r = k > 3 ? fact(k) : -1;
Prepack 0.2.54the last release, 2018
Fails with an error
No output
stderr
In input file recursion-fact.js(3:36) FatalError PP0045: Maximum stack depth exceeded (https://github.com/facebook/prepack/wiki/PP0045)
    at fact (recursion-fact.js:3:27)
    at fact (recursion-fact.js:3:27)
    at fact (recursion-fact.js:3:27)
    at fact (recursion-fact.js:3:27)
    at fact (recursion-fact.js:3:27)
… 108 more lines
exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var fact, r, k;
(function () {
  var _$4 = this;

  var _9 = function (n) {
    return n <= 1 ? 1 : n * fact(n - 1);
  };

  _$4.fact = _9;
  var _1 = k0;
  _$4.k = _1;

  var _3 = _1 > 3;

  if (_3) {
    var _$3 = _9(_1);
  }

  _$4.r = _3 ? _$3 : -1;
}).call(this);
20 lines · 269 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
k0 = 6720no program720

All 13 recursion cases pass, including mutual recursion, tree walks, a recursive-descent parser over an unknown string, and deep clone. #543’s original one-liner is worth a footnote: by 0.2.54 it no longer exploded to 1,065 lines, because a later fix removed the dead Date.now() calls. The underlying problem, unrolling recursion on unknown values, is what the cases above measure.

P3 · Control-flow joins and the fuzzer

When an if on an unknown value contains a return, Prepack has to carry both futures forward and join them later. The original design spread that across ten completion classes, host exceptions, and a piece of mutable state on the realm. The fuzzer’s 2018 bugs live here:

#2354, found by the original fuzzer. The 2018 repro used booleans; with other falsy values 0.2.54 still returns NaN where the program returns false.
Inputjoins-2354.js
(function() {
  "use strict";
  function main(a1, a2, a3) {
    if (a1) {
      if (a2) {
        return false;
      }
    }
    if (!a3) {
      return false;
    }
    return true;
  }
  if (global.__optimize) __optimize(main);
  global.main = main;
})();
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
(function () {
  "use strict";

  var _$0 = this;
  var _1 = function (a1, a2, a3) {
    var _7 = !a2;
    var _5 = a1 && _7;
    var _9 = !a1;
    var _4 = _5 || _9;
    var _3 = _4 && a3;
    var _2 = _4 ? _3 : false;
    return _2;
  };
  _$0.main = _1;
}).call(this);
15 lines · 272 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
(function () {
  "use strict";

  var _$0 = this;

  _$0.main = function (a1, a2, a3) {
    return a1 && !a2 || !a1 ? !!a3 : false;
  };
}).call(this);
9 lines · 152 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
main(false, false, false)falsefalsefalse
main(0, "", NaN)falseNaN ✗ differsfalse
#2355: if (a) return true; return false; must return a boolean.
Inputjoins-2355.js
(function() {
  "use strict";
  function main(a1) {
    if (a1) {
      return true;
    }
    return false;
  }
  if (global.__optimize) __optimize(main);
  global.main = main;
})();
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
(function () {
  "use strict";

  var _$0 = this;
  var _1 = function (a1) {
    return a1;
  };
  _$0.main = _1;
}).call(this);
9 lines · 129 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
(function () {
  "use strict";

  var _$0 = this;

  _$0.main = function (a1) {
    return !!a1;
  };
}).call(this);
9 lines · 117 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
[1, 0, "", "x", null, NaN].map(main)[ true, false, false, true, false, false ][ 1, 0, '', 'x', null, NaN ] ✗ differs[ true, false, false, true, false, false ]
A break out of a labeled block under an unknown condition. 0.2.54 drops the break and folds the function to a constant.
Inputjoins-labeled.js
function f(a, b) {
  var r = "start";
  block: {
    if (a) break block;
    r += "-1";
    if (b) break block;
    r += "-2";
  }
  return r;
}
if (global.__optimize) __optimize(f);
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
var f;
(function () {
  var _$0 = this;
  var _1 = function (a, b) {
    return "start-1-2";
  };
  _$0.f = _1;
}).call(this);
8 lines · 127 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var f;
(function () {
  var _$0 = this;

  _$0.f = function (a, b) {
    return !a ? !b ? "start-1-2" : "start-1" : "start";
  };
}).call(this);
8 lines · 145 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
f(true, true), f(false, true), f(false, false)start,start-1,start-1-2start-1-2,start-1-2,start-1-2 ✗ differsstart,start-1,start-1-2

These trace back to a handful of root causes: the saved-completion state was never scoped at switch, labeled-statement and try boundaries; two simplifier rules erased boolean conversions; a joined completion reported only its first branch’s value. Fixing those made room for the two features the tracker had asked for. switch on an unknown value (#1378, “level 4 (hard)”) now handles fall-through and returns:

Inputjoins-switch-1378.js
function weight(v) {
  var w = 0;
  switch (v) {
    case 1:
      w += 1;
    case 2:
      w += 10;
      break;
    case 3:
      return "three";
    case 4:
    case 5:
      w += 100;
    default:
      w += 1000;
  }
  return w;
}
if (global.__optimize) __optimize(weight);
Prepack 0.2.54the last release, 2018
Fails with an error
No output
stderr
In input file joins-switch-1378.js(4:10) FatalError PP0001: This operation is not yet supported on abstract value   (https://github.com/facebook/prepack/wiki/PP0001)
    at weight (joins-switch-1378.js:4:10)
Prepack failed, reporting 1 fatal error.
exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var weight;
(function () {
  var _$0 = this;

  _$0.weight = function (v) {
    var _J = 1 === v;

    var _H = 2 === v;

    return _J || 1 !== v && (_H || 2 !== v && 3 !== v) ? _J ? 11 : _H ? 10 : 4 === v ? 1100 : 5 === v ? 1100 : 1000 : "three";
  };
}).call(this);
12 lines · 269 bytes · exit 0 · 0.6 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
[0, 1, 2, 3, 4, 5, "1"].map(weight)[ 1000, 11, 10, 'three', 1100, 1100, 1000 ]no program[ 1000, 11, 10, 'three', 1100, 1100, 1000 ]

And try/catch around code that might throw, which used to be a hard error (PP0021, #2120), is now emitted as a residual try:

0.2.54 reports PP0021 and prints code without the catch; that code throws where the original returns 42.
Inputjoins-try-2120.js
function f(g) {
  try {
    return g();
  } catch (e) {
    return 42;
  }
}
if (global.__optimize) __optimize(f);
Prepack 0.2.54the last release, 2018
Reports an error, prints wrong code anyway
var f;
(function () {
  var _$1 = this;
  var _1 = function (g) {
    var _$0 = g();
    return _$0;
  };
  _$1.f = _1;
}).call(this);
stderr
In input file joins-try-2120.js(3:12) RecoverableError PP0021: Possible throw inside try/catch is not yet supported (https://github.com/facebook/prepack/wiki/PP0021)
    at f (joins-try-2120.js:3:12)
Prepack failed, reporting 1 recoverable error.
9 lines · 135 bytes · exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var f;
(function () {
  var _$3 = this;

  _$3.f = function (g) {
    {
      var _$1;

      try {
        var _$0 = g();
      } catch (e) {
        _$1 = [e];
      }
    }

    var _3 = void 0 !== _$1;

    return _3 ? 42 : _$0;
  };
}).call(this);
20 lines · 253 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
f(() => 1), f(() => { throw 0 })1,42Error: x ✗ differs1,42

A differential fuzzer generated 48,898 programs covering loops, labels, switch, try, objects, arrays and closures. It found 1,260 silently wrong programs and 469 internal invariant failures, which minimized to dozens of distinct bugs. Every one became a corpus case: 44 now live in test/serializer/hard/fuzz/. On those 44, 0.2.54 is wrong on 19 and crashes on 25. The revived build passes 43.

P4 · Path conditions, checked by Z3

Prepack’s logic engine was a set of hand-written rewrite and implication rules. Rather than write better rules, the revived Prepack asks a real SMT solver. Z3 runs as a persistent child process behind two named pipes, so Prepack, which is synchronous throughout, can ask it questions with blocking reads (about 1 ms per query). Every JavaScript value is encoded as a tagged datatype with exact IEEE-754 special cases for NaN, ±Infinity and truthiness, and the encoding itself was tested against Node on 3,000 random expressions.

Z3 then did two jobs. In normal runs it answers implication and feasibility questions the rules can’t, and prunes branches that can never execute. In audit mode, it checks every rewrite the old rules perform: if X → Y and X ≠ Y is satisfiable, that is a bug, with a counterexample. Run over the test suite and the corpus, the audit produced 3,993 failure reports from 20 distinct places in the code. A few of them:

Rule in the original simplifierZ3 counterexample
c ? true : false  →  cc = 0: left is false, right is 0
(0 !== x) implies x is truthyx = false (also "", null, NaN)
x != null implies x is truthyx = false
!(x != null) implies x === nullx = undefined
x == null implies x === nullx = undefined
x === +0 implies !(x === -0)x = 0
((c ? 1 : y) === y)  →  !cc = false, y = NaN

Thirteen unsound rules were fixed or deleted, and the audit now reports zero failures across 34,047 checks. Here is one the fuzzer found and the audit explained:

!(a == null ? 0 : a) was simplified to a == null, which is wrong for every falsy non-null value.
Inputpaths-negation.js
(function() {
  "use strict";
  function main(a) {
    return !(a == null ? 0 : a);
  }
  if (global.__optimize) __optimize(main);
  global.main = main;
})();
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
(function () {
  "use strict";

  var _$0 = this;
  var _1 = function (a) {
    var _2 = a == null;
    return _2;
  };
  _$0.main = _1;
}).call(this);
10 lines · 152 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
(function () {
  "use strict";

  var _$0 = this;

  _$0.main = function (a) {
    return !(a == null ? 0 : a);
  };
}).call(this);
9 lines · 132 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
[0, "", false, NaN, null, 1].map(main)[ true, true, true, true, true, false ][ false, false, false, false, true, false ] ✗ differs[ true, true, true, true, true, false ]

The cost is real but bounded: on the full serializer suite, Z3 adds about two and a half minutes to a four and a half minute run, because 98.8% of queries hit a cache. What Z3 can’t do here is also documented: it reasons about numbers as exact reals, so it believes x + 1 > x even at 253.

P5 · Numeric facts

The fix is an interval domain: every numeric abstract value carries bounds, whether it is an integer, and whether it might be NaN or -0, derived from the expression and refined by the current path conditions. An array whose length is “0 or 1” can then be pushed to, by splitting on the few possible lengths:

#1066, opened by Nikolai Tillmann in 2017.
Inputranges-1066.js
var a = [];
var x = global.__abstract ? __abstract("boolean", "x0") : x0;
(function() {
  if (x) a.push(42);
  a.push(23);
})();
Prepack 0.2.54the last release, 2018
Fails with an error
No output
stderr
In input file ranges-1066.js(5:10) FatalError PP0001: This operation is not yet supported on abstract value x0  (https://github.com/facebook/prepack/wiki/PP0001)
    at push (native)
    at ranges-1066.js:5:3
    at ranges-1066.js:3:1
Prepack failed, reporting 1 fatal error.
exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var x, a;
(function () {
  var _$0 = this;

  var _5 = x0;
  var _0 = [!_5 ? 23 : 42];
  if (_5) _0[1] = 23;
  _$0.a = _0;
  _$0.x = _5;
}).call(this);
10 lines · 152 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
x0 = true42,23 | length 2no program42,23 | length 2
x0 = false23 | length 1no program23 | length 1

The same machinery handles table[n & 3], clamped loop bounds and array methods on short abstract-length arrays. Along the way it turned up a bug unrelated to abstract values: the original Array.prototype.shift started its copy loop at the wrong index, writing the removed element to O["-1"].

P6 · Built-ins and the outer fixed point

If we havoced everything that is read by an optimized function, we'll probably find it impossible to do any optimizations at all.

Herman Venter, Prepack lead · #1891, May 8, 2018

That was the bind: anything handed to code Prepack couldn’t see had to be havoced, and havocing too much makes the analysis useless. #2319 listed Facebook’s top built-in bail-outs: RegExp.prototype.test (275), Array.prototype.indexOf (201), parseInt (176) and Math.abs (58). This is the kind of tedious, spec-heavy work a small team never has time for: 81 built-ins now carry declarative effect summaries (what they coerce, whether they can throw, whether they can run user code), each with a differential test.

All of #2319’s top four in one function. 0.2.54 reports three fatal errors, then prints code that happens to work.
Inputbuiltins-2319.js
function classify(s, n) {
  var re = /^[a-z]+$/;
  var meta = { kind: "other", size: 0 };
  if (re.test(s)) meta.kind = "word";
  meta.size = parseInt(n, 10) + Math.abs(-2);
  return meta.kind + ":" + meta.size + ":" + [10, 12, 14].indexOf(meta.size);
}
if (global.__optimize) __optimize(classify);
Prepack 0.2.54the last release, 2018
Reports an error (exit 1), then prints its fallback
var classify;
(function () {
  var _$9 = this;
  var _$A = _$9.parseInt;
  var _1 = function (s, n) {
    var _2 = /^[a-z]+$/;
    var _$0 = _2.test(s);
    var _F = _$A;
    var _$1 = _F(n, 10);
    var _$2 = _$1 + 2;
    var _N = _$0 ? "word" : "other";
    var _M = _N + ":";
    var _$3 = _M + _$2;
    var _6 = [10, 12, 14];
    _6[0] = 10;
    _6[1] = 12;
    _6[2] = 14;
    var _$4 = _6.indexOf(_$2);
    var _I = _$3 + ":";
    var _$5 = _I + _$4;
    return _$5;
  };
  _$9.classify = _1;
}).call(this);
stderr
In input file builtins-2319.js(4:15) FatalError PP0001:  (https://github.com/facebook/prepack/wiki/PP0001)
    at test (native)
    at classify (builtins-2319.js:4:7)
In input file builtins-2319.js(5:27) FatalError PP0001:  (https://github.com/facebook/prepack/wiki/PP0001)
    at parseInt (native)
    at classify (builtins-2319.js:5:15)
… 4 more lines
24 lines · 514 bytes · exit 1 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var classify;
(function () {
  var _$2 = this;

  _$2.classify = function (s, n) {
    var _$0 = /^[a-z]+$/.test(s);

    var _$1 = _$2.parseInt(n, 10);

    var _C = 2 + _$1;

    return (_$0 ? "word:" : "other:") + _C + ":" + (10 === _C ? 0 : 12 === _C ? 1 : 14 === _C ? 2 : -1);
  };
}).call(this);
14 lines · 302 bytes · exit 0 · 0.7 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
classify("abc", "10"), classify("A1", "12px")word:12:1,other:14:2word:12:1,other:14:2word:12:1,other:14:2

The lazy cache from #1750 needed the “outer fixed point” Venter called a bridge too far. The revived build evaluates the optimized function while recording which outer state it reads; any state it both reads and writes is made abstract, and the function is evaluated again until nothing changes. 0.2.54 baked the first call’s cache state into every call:

#1750. Both builds print a warning about the mutation; only one gets the counts right.
Inputhavoc-1750.js
var cache = {};
function getEntry(arg) {
  cache[arg] = cache[arg] || { n: 0, key: arg };
  cache[arg].n++;
  return cache[arg].key + cache[arg].n;
}
if (global.__optimize) __optimize(getEntry);
Prepack 0.2.54the last release, 2018
Succeeds with a warning: wrong result
var getEntry, cache;
(function () {
  var _$O = this;
  var _$P = _$O.Object;
  var _$Q = _$P.prototype;
  var _3 = function (arg) {
    var _$0 = "" + arg;
    var _m = _$Q;
    var _$1 = _m[_$0];
    var _$2 = "" + arg;
    var _$4 = "" + arg;
    var _$5 = "" + arg;
    var _$7 = _1[_$5];
    var _5 = _$4 === _$5;
    if (_5) {
      if (_$1) {
        var _$8 = _$1.n;
      }
    } else {
      var _$9 = _$7.n;
    }
    var _A = _$1 ? _$8 : 0;
    var _9 = _5 ? _A : _$9;
    var _$A = _9 + 1;
    var _E = {};
    _E.n = 0;
    _E.key = arg;
    var _I = _$1 || _E;
    var _H = _5 ? _I : _$7;
    _H.n = _$A;
    var _$B = "" + arg;
    var _$D = _1[_$B];
    var _M = _$4 === _$B;
    if (_M) {
      if (_$1) {
        var _$E = _$1.key;
      } else {
        var _$F = _E.key;
      }
    } else {
      var _$G = _$D.key;
    }
    var _$H = "" + arg;
    var _$J = _1[_$H];
    var _O = _$4 === _$H;
    if (_O) {
      if (_$1) {
        var _$K = _$1.n;
      } else {
        var _$L = _E.n;
      }
    } else {
      var _$M = _$J.n;
    }
    var _T = _$1 ? _$E : _$F;
    var _S = _M ? _T : _$G;
    var _Y = _$1 ? _$K : _$L;
    var _X = _O ? _Y : _$M;
    var _$N = _S + _X;
    _1[_$4] = _I;
    return _$N;
  };
  var _1 = {};
  _$O.getEntry = _3;
  _$O.cache = _1;
}).call(this);
stderr
In input file havoc-1750.js(1:13) Warning PP1007: side-effects from mutating a property  at location havoc-1750.js[1:12] (https://github.com/facebook/prepack/wiki/PP1007)
Prepack succeeded, reporting 1 warning.
66 lines · 1,309 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var getEntry, cache;
(function () {
  var _$Y = this;

  var _1 = {};

  _$Y.getEntry = function (arg) {
    var _$O = _1[arg];
    var _$P = _1[arg];
    var _5 = {};
    _5.n = 0;
    _5.key = arg;

    var _9 = _$O || _5;

    _1[arg] = _9;
    var _$Q = _1[arg];
    var _$R = _$Q.n;

    var _$S = +_$R;

    var _E = 1 + _$S;

    _$Q.n = _E;
    var _$T = _1[arg];
    var _$U = _$T.key;
    var _$V = _1[arg];
    var _$W = _$V.n;

    var _$X = _$U + _$W;

    return _$X;
  };

  _$Y.cache = _1;
}).call(this);
stderr
In input file havoc-1750.js(1:13) Warning PP1007: side-effects from mutating a property  at location havoc-1750.js[1:12] (https://github.com/facebook/prepack/wiki/PP1007)
Prepack succeeded, reporting 1 warning.
36 lines · 521 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
getEntry("a") ×2, getEntry("b"), getEntry("a")a1,a2,b1,a3a1,a1,b1,a1 ✗ differsa1,a2,b1,a3

Two parts of P6 remain open in general. Distinct object parameters are still assumed not to alias unless something proves otherwise, and a method call on an untyped receiver (arr.indexOf(x) where arr could be anything) still has to assume the worst about its arguments.

P7 · The environment

Some previous work on analyzing JavaScript has already highlighted just how difficult things become once you try to support all of JavaScript and you have to deal with untyped call backs. The conclusion reached in those efforts was that, in the end, the analyzer knows nothing much.

Herman Venter, Prepack lead · #24, July 29, 2018

The original models were closed-world: a global Prepack didn’t know about was assumed not to exist. That produced the worst kind of failure, a build that succeeds with a warning and ships a bundle that throws on startup:

0.2.54 compiles a reference to process into a program whose only action is to throw a ReferenceError. The revived build leaves environment reads residual.
Inputenv-process.js
var isProd = process.env.NODE_ENV === "production";
var config = {
  minify: isProd,
  logLevel: process.env.DEBUG === "1" ? "debug" : isProd ? "error" : "info",
};
Prepack 0.2.54the last release, 2018
Succeeds with a warning: wrong result
var config, isProd;
(function () {
  var $$0 = {
    enumerable: false,
    configurable: true,
    writable: true
  };
  var _$0 = this;
  var _$1 = _$0.ReferenceError;
  var _$2 = _$1.prototype;
  var _$3 = _$0.Object;
  var _$4 = _$3.defineProperty;
  var __constructor = function () {};
  var _3 = _$2;
  var _0 = (__constructor.prototype = _3, new __constructor());
  $$0.value = "process is not defined", _$4(_0, "message", $$0);
  $$0.value = "ReferenceError: process is not defined\n    at env-process.js:1:14", _$4(_0, "stack", $$0);
  throw _0;
}).call(this);
stderr
In input file env-process.js(1:14) Warning PP0023: Program may terminate with exception: process is not defined
  ReferenceError: process is not defined
    at env-process.js:1:14 (https://github.com/facebook/prepack/wiki/PP0023)
Prepack succeeded, reporting 1 warning.
19 lines · 570 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var config, isProd;
(function () {
  var _$4 = this;

  var _$0 = process.env;
  var _$1 = _$0.NODE_ENV;

  var _0 = "production" === _$1;

  _$4.isProd = _0;
  var _$2 = process.env;
  var _$3 = _$2.DEBUG;
  _$4.config = {
    minify: _0,
    logLevel: _$3 === "1" ? "debug" : _0 ? "error" : "info"
  };
}).call(this);
17 lines · 320 bytes · exit 0 · 0.5 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
in Node with NODE_ENV=production, DEBUG=1{ minify: true, logLevel: 'debug' }ReferenceError: process is not defined ✗ differs{ minify: true, logLevel: 'debug' }
Feature detection folded against Prepack’s own environment: in 0.2.54, a browser with fetch is told it has none.
Inputenv-window.js
var root = typeof window !== "undefined" ? window : global;
var api = {
  hasFetch: typeof root.fetch === "function",
  name: "api",
};
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
var api, root;
(function () {
  var _$0 = this;
  var _5 = _$0;
  _$0.root = _5;
  var _1 = {
    hasFetch: false,
    name: "api"
  };
  _$0.api = _1;
}).call(this);
11 lines · 167 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var api, root;
(function () {
  var _$2 = this;

  var _7 = _$2;
  _$2.root = _7;
  var _$0 = _7.fetch;

  var _$1 = typeof _$0;

  _$2.api = {
    hasFetch: "function" === _$1,
    name: "api"
  };
}).call(this);
15 lines · 214 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
in a browser (window === this, fetch defined){ hasFetch: true, name: 'api' }{ hasFetch: false, name: 'api' } ✗ differs{ hasFetch: true, name: 'api' }

Three changes fix it. Unknown globals are now open-world: they become residual reads. Models for the DOM, Node and Intl are generated from TypeScript’s lib.dom.d.ts and @types/node (77 interfaces, 102 DOM globals), which, as it happens, is what a commenter on #24 suggested in 2017. And specializing on the environment is now explicit: --assume folds an assumption and emits a runtime guard, with the original code as the fallback:

--assume 'process.env.NODE_ENV="production"' --assume timezoneOffset=0. The time-zone guard checks two dates half a year apart so daylight saving time can’t pass it by accident. On a machine in India the guard fails and the original code runs.
Inputenv-guard.js
var mode = process.env.NODE_ENV === "production" ? "prod" : "dev";
var newYear = new Date(2020, 0, 1).getTime();
var offset = new Date(2020, 6, 1).getTimezoneOffset();
Revived Prepackthis project
Succeeds: same result as the original
var offset, newYear, mode;

if (typeof process !== "undefined" && process != null && process.env != null && process.env.NODE_ENV === "production" && new Date(0).getTimezoneOffset() === 0 && new Date(1593561600000).getTimezoneOffset() === 0 && new Date(1577836800000).getTimezoneOffset() === 0 && new Date(1593561600000).getTimezoneOffset() === 0) {
  (function () {
    var _$1 = this;

    _$1.mode = "prod";
    _$1.newYear = 1577836800000;
    _$1.offset = 0;
  }).call(this);
} else {
  var mode = process.env.NODE_ENV === "production" ? "prod" : "dev";
  var newYear = new Date(2020, 0, 1).getTime();
  var offset = new Date(2020, 6, 1).getTimezoneOffset();
}
15 lines · 665 bytes · exit 0 · 0.3 s
RunOriginal programRevived Prepack output
NODE_ENV=production, TZ=UTCprod,1577836800000,0prod,1577836800000,0
NODE_ENV=development, TZ=Asia/Kolkatadev,1577817000000,-330dev,1577817000000,-330

P8 · Output size

The revived Prepack attacks bloat at three levels: serializer peepholes (dead intermediate writes, compact Error objects, Object.create instead of a prototype-swapping constructor), a cost model that decides at serialization time whether to write out a computed object graph or the code that computed it, and limits on inlining. The cost model is the important one:

A 1,000-iteration init loop. 0.2.54 writes the table out, along with every intermediate value of i. The revived build verifies the loop is pure and self-contained, then keeps the loop. Long outputs are trimmed where marked; nothing else is edited.
Inputsize-table.js
var table = [];
for (var i = 0; i < 1000; i++) {
  table.push((i * i) % 7);
}
Prepack 0.2.54the last release, 2018
Succeeds: same result as the original
var i, table;
(function () {
  var _$0 = this;
  var _0 = [0, 1, 4, 2, 2, 4, 1, 0, 1, 4, 2, 2, 4, 1, 0, 1, 4, 2, 2, 4, 1, 0, 1, 4, 2, 2, 4, 1, 0, 1, 4, 2, 2, 4, 1, 0, 1, 4, 2, 2, 4, 1, 0, 1, 4, 2, 2, 4, 1, 0 /* … 2,852 more characters */
  _$0.table = _0;
  _$0.i = 0;
… 997 lines omitted …
  _$0.i = 998;
  _$0.i = 999;
  _$0.i = 1000;
}).call(this);
1,007 lines · 17,999 bytes · exit 0 · 0.5 s
Revived Prepackthis project
Succeeds: same result as the original
var i, table;
(function () {
  var _$0 = this;

  _$0.table = [];

  for (i = 0; i < 1000; i++) {
    table.push(i * i % 7);
  }
}).call(this);
10 lines · 144 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
table.length, sum1000 entries, sum 20011000 entries, sum 20011000 entries, sum 2001
An Error created at init. 0.2.54 rebuilds it as a plain object with the build machine’s stack trace baked in, so it is no longer a real Error. The revived build emits new TypeError(...).
Inputsize-error.js
var INVALID = new TypeError("Invalid config");
global.check = function (c) {
  if (!c) throw INVALID;
  return c;
};
Prepack 0.2.54the last release, 2018
Succeeds: wrong result
var INVALID;
(function () {
  var $$0 = {
    enumerable: false,
    configurable: true,
    writable: true
  };
  var _$0 = this;
  var _$1 = _$0.TypeError;
  var _$2 = _$1.prototype;
  var _$3 = _$0.Object;
  var _$4 = _$3.defineProperty;
  var _6 = function (c) {
    if (!c) throw INVALID;
    return c;
  };
  var __constructor = function () {};
  var _5 = _$2;
  var _0 = (__constructor.prototype = _5, new __constructor());
  $$0.value = "Invalid config", _$4(_0, "message", $$0);
  $$0.value = "TypeError: Invalid config\n    at size-error.js:1:15", _$4(_0, "stack", $$0);
  _$0.INVALID = _0;
  _$0.check = _6;
}).call(this);
24 lines · 634 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
var INVALID;
(function () {
  var _$0 = this;

  var _$1 = _$0.TypeError;
  _$0.INVALID = new _$1("Invalid config");

  _$0.check = function (c) {
    if (!c) throw INVALID;
    return c;
  };
}).call(this);
12 lines · 208 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
what check() throws[object Error] Invalid config instanceof TypeError: true[object Object] Invalid config instanceof TypeError: true ✗ differs[object Error] Invalid config instanceof TypeError: true

The same recomputation logic fixed the biggest real-world regression. lodash builds its whole API inside runInContext() and keeps a reference to that factory, so materializing the result paid for the factory and its output twice. Re-emitting the call instead took lodash from 3.16× to 1.05× its minified size; curried function factories took ramda from 2.54× to 1.23×. Across the corpus, total output shrank by 40.5%, and no case is larger than twice its input.

Modern JavaScript

Venter expected updating Prepack to modern JavaScript to “be a lot of work,” and it was, but it was the kind of work that is mostly transcription. The parser now accepts ES2022 class fields and private members, optional catch bindings, ??, optional chaining and BigInt. Generators that are created and finished during initialization are lowered to a state machine and run, and settled Promises are serialized as Promise.resolve(…) after draining a real job queue. A generator still suspended at the end of initialization is refused on purpose: its state lives in lowered closures with no equivalent in the source, and emitting a runtime to resume it would trade fidelity for coverage. Venter’s question from #712, how to iterate a generator through an if whose condition is abstract, is still open: generators run at build time only when everything about them is concrete.

A class with private members. 0.2.54 prints a program that is a SyntaxError.
Inputprivate-class.js
const KEYWORDS = new Set(["let", "const", "if"]);
class Lexer {
  #pos = 0;
  constructor(src) { this.src = src; }
  #peek() { return this.src[this.#pos]; }
  static isKeyword(w) { return KEYWORDS.has(w); }
  word() {
    let w = "";
    while (this.#pos < this.src.length && /\w/.test(this.#peek())) w += this.src[this.#pos++];
    return w;
  }
  get position() { return this.#pos; }
}
globalThis.lex = (s) => {
  const l = new Lexer(s);
  const w = l.word();
  return (Lexer.isKeyword(w) ? "kw:" : "id:") + w + "@" + l.position;
};
Prepack 0.2.54the last release, 2018
Succeeds with a warning: wrong result
let KEYWORDS;
(function () {
  var $$0 = {
    enumerable: false,
    configurable: true,
    writable: true
  };
  var _$0 = this;
  var _$1 = _$0.ReferenceError;
  var _$2 = _$1.prototype;
  var _$3 = _$0.Object;
  var _$4 = _$3.defineProperty;
  var _$5 = _$0.Set;
  var __constructor = function () {};
  var _4 = class {
    constructor(src) {
      this.src = src;
    }
    static isKeyword(w) {
      return KEYWORDS.has(w);
    }
    word() {
      let w = "";
      while (this.#pos < this.src.length && /\w/.test(this.#peek())) w += this.src[this.#pos++];
      return w;
    }
    get position() {
      return this.#pos;
    }
  };
  _4.prototype.position = void 0;
  _$0.Lexer = _4;
  var _7 = _$2;
  var _1 = (__constructor.prototype = _7, new __constructor());
  $$0.value = "globalThis is not defined", _$4(_1, "message", $$0);
  $$0.value = "ReferenceError: globalThis is not defined\n    at private-class.js:14:1", _$4(_1, "stack", $$0);
  throw _1;
  var _B = new _$5(["let", "const", "if"]);
  KEYWORDS = _B;
}).call(this);
stderr
In input file private-class.js(14:1) Warning PP0023: Program may terminate with exception: globalThis is not defined
  ReferenceError: globalThis is not defined
    at private-class.js:14:1 (https://github.com/facebook/prepack/wiki/PP0023)
Prepack succeeded, reporting 1 warning.
40 lines · 1,044 bytes · exit 0 · 0.3 s
Revived Prepackthis project
Succeeds: same result as the original
let KEYWORDS;
(function () {
  var _$0 = this;

  var _$1 = _$0.Set;

  var _2 = class {
    constructor(src) {
      this.src = src;
    }

    #pos = 0;

    #peek() {
      return this.src[this.#pos];
    }

    static isKeyword(w) {
      return KEYWORDS.has(w);
    }

    word() {
      let w = "";

      while (this.#pos < this.src.length && /\w/.test(this.#peek())) w += this.src[this.#pos++];

      return w;
    }

    get position() {
      return this.#pos;
    }

  };

  _$0.Lexer = _2;

  _$0.lex = s => {
    const l = new Lexer(s);
    const w = l.word();
    return (Lexer.isKeyword(w) ? "kw:" : "id:") + w + "@" + l.position;
  };

  KEYWORDS = new _$1(["let", "const", "if"]);
}).call(this);
45 lines · 714 bytes · exit 0 · 0.4 s
RunOriginal programPrepack 0.2.54 outputRevived Prepack output
lex("let x"), lex("abc")kw:let@3,id:abc@3SyntaxError: Private field '#pos' must be declared in an enclosing class ✗ differskw:let@3,id:abc@3

Real-world results

The benchmark corpus has 22 real libraries and app bundles, from 3.6 KB to 8.9 MB, each with a smoke test whose output digest must match the original’s in a fresh node process and in a vm context. Each is compiled under six option profiles (default, browser, seeded random, old JavaScriptCore, Node/React, lazy objects): 132 compilations. Results on the revived build:

0wrong outputs in 132 compilations
18of 22 compile and match in at least one profile (15 with defaults)
4stop with a clean diagnostic

Revived Prepack on real code, browser profile. “Code cache” is load plus smoke test in a vm context with V8’s code cache, the setting Prepack was designed for; “exec only” excludes compilation entirely; “cold” is a fresh node process minus Node’s own 27 ms startup, shown only where the original takes at least 5 ms. Medians of 30 runs (15 for inputs over 750 KB) on a shared 4-core machine.

WorkloadKBProfiles OKgzip size vs originalCode cacheExec onlyCold node
lodash533.14/60.98×3.2 → 3.4 ms 0.92×1.04×16 → 17 ms 0.95×
underscore67.36/61.12×0.61 → 0.59 ms 1.03×1.03×under 5 ms
ramda342.96/61.10×0.82 → 0.45 ms 1.84×1.89×under 5 ms
immutable175.46/60.99×1.4 → 1.3 ms 1.12×1.12×6.9 → 6.5 ms 1.07×
moment637.86/61.32×12 → 2.7 ms 4.28×4.32×25 → 26 ms 0.97×
chroma-js109.26/60.94×0.97 → 0.90 ms 1.07×1.06×under 5 ms
acorn240.96/61.07×1.9 → 1.5 ms 1.22×1.27×7.9 → 7.7 ms 1.02×
crc-323.66/655.12×0.34 → 0.22 ms 1.52×1.69×under 5 ms
marked49.45/60.99×1.2 → 1.2 ms 0.99×1.01×35 → 36 ms 0.97×
d3-scale148.95/61.25×0.97 → 0.61 ms 1.59×1.70×under 5 ms
redux18.16/60.89×0.33 → 0.28 ms 1.18×1.19×under 5 ms
handlebars-app211.95/61.02×3.4 → 1.1 ms 3.13×3.03×16 → 4.2 ms 3.88×
preact-ssr-app224/61.01×0.41 → 0.36 ms 1.15×1.11×under 5 ms
react-ssr-app475/61.17×0.89 → 0.59 ms 1.50×1.52×under 5 ms
yaml-router-app157.16/61.02×1.8 → 0.62 ms 2.95×2.60×6.5 → 3.2 ms 2.04×
moment-timezone913.34/61.34×7.8 → 5.4 ms 1.44×1.37×24 → 20 ms 1.16×
prettier1,065.60/6Stops: PP0001: This operation is not yet supported on abstract value global
highlight-js1,834.55/62.08×16 → 12 ms 1.30×1.31×90 → 97 ms 0.93×
three1,914.45/61.18×4.7 → 3.5 ms 1.34×1.36×35 → 31 ms 1.10×
mathjs2,117.90/6Stops: PP0001: This operation is not yet supported on abstract value
babel-standalone5,248.90/6Stops: PP0001: This operation is not yet supported on abstract value
typescript8,899.10/6Stops: PP0001: This operation is not yet supported on abstract value

Two things stand out. Where initialization is real work, skipping it pays: moment registers well over a hundred locales at load and starts 4.3× faster from the code cache, handlebars and the YAML router app about 3×. Where a library mostly defines functions, there is little to skip and the result is a wash (lodash, marked). Size is now close to the original for most inputs; the exceptions are deliberate trades the cost model made, such as crc-32, whose 4,352 precomputed CRC constants are 44 KB gzipped against 0.8 KB of source in exchange for skipping the computation. The four failures stop where open-world environment values reach loops Prepack can’t summarize yet (for @babel/standalone, a localStorage read; with localStorage, navigator and document assumed absent it compiles, in about 279 seconds).

React, revisited

The React compiler of 2018 tried to fold React itself. The revival took a narrower route: specialize React 18 server rendering of a known app for unknown request data. The app is a 30-component e-commerce page (the same one behind the Northstar demo linked below); the data is a different product listing on every request. Instead of evaluating the real react-dom/server, which keeps its state in long-lived queues Prepack can’t summarize, we ported React’s server renderer into about 1,300 lines written in a style Prepack can evaluate: every function restores what it changes. The attribute tables are copied verbatim from react-dom 18.3.1. Prepack then specializes the whole render for a data schema inferred from training inputs. Here is one small component and the code Prepack produced for one place it is used:

One component from the benchmark app (app.jsx) and the corresponding fragment of the revived Prepack’s generated renderer (one expression, verbatim; the full program is 83,754 bytes). React is gone: the result is string concatenation with the branches the data needs.
InputStockBadge in app.jsx
function StockBadge(props) {
  var stock = props.stock;
  if (stock <= 0) {
    return (
      <span className="ns-stock ns-stock-out" data-stock-state="out">
        Out of stock
      </span>
    );
  }
  if (stock <= 5) {
    return (
      <span className="ns-stock ns-stock-low" data-stock-state="low">
        Only {stock} left
      </span>
    );
  }
  return (
    <span className="ns-stock ns-stock-in" data-stock-state="in">
      In stock
      {typeof props.shipsAtSec === "number"
        ? " · Ships " + formatDateUTC(props.shipsAtSec, false)
        : ""}
    </span>
  );
}
Revived Prepackreact-ssr specialization
(_$5E <= 0
  ? "<span class=\"ns-stock ns-stock-out\" data-stock-state=\"out\">Out of stock</span>"
  : _$5E <= 5
  ? "<span class=\"ns-stock ns-stock-low\" data-stock-state=\"low\">Only <!-- -->" + _$5E + "<!-- --> left</span>"
  : "<span class=\"ns-stock ns-stock-in\" data-stock-state=\"in\">In stock</span>")
line breaks added at ? and : for reading

The examples below are bigger. Each is a small app built from real components of the same page, compiled on its own with the same compiler and training inputs, so the renderer Prepack generated for those components can be read in one place. The inputs are the components exactly as they appear in app.jsx, plus a few lines of wrapper; the outputs are excerpts of the generated code, with the omitted lines marked.

A list of unknown length

In 2018 this is where the React compiler stopped: components with loops were excluded from compilation, and a map() callback over unknown data was left unevaluated, dragging the original component into the bundle (#2141, #2142). Here the callback is specialized once and run by a residual loop (__ssrMapJoin). Each item becomes a single string expression: the badge conditions turn into ternaries, the title is escaped at runtime, and React’s <!-- --> separators around the stock count appear exactly where React puts them.

A product list with per-item badges and stock state (StockBadge is the component shown above). Checked against renderToString on the benchmark’s 500 check inputs: 500/500 byte-identical, 0 fallbacks. This component alone renders 4.9× faster than renderToString (2.8× including the per-request schema check).
InputProductList, ProductBadges
function ProductList(props) {
  return (
    <ul className="ns-grid">
      {props.products.map(function (p) {
        return (
          <li key={p.sku} className={cx("ns-card", { "ns-card-out": p.stock <= 0 })}>
            <ProductBadges product={p} />
            <h3 className="ns-card-title">{p.title}</h3>
            <StockBadge stock={p.stock} shipsAtSec={p.shipsAtSec} />
          </li>
        );
      })}
    </ul>
  );
}
function App(props) {
  return <ProductList products={props.data.products} />;
}

function ProductBadges(props) {
  var p = props.product;
  var onSale =
    typeof p.originalPrice === "number" && p.originalPrice > p.price;
  if (!p.featured && !p.clearance && !onSale) {
    return null;
  }
  return (
    <div className="ns-badges">
      {p.featured ? (
        <span className="ns-badge ns-badge-featured">Featured</span>
      ) : null}
      {onSale ? <span className="ns-badge ns-badge-sale">Sale</span> : null}
      {p.clearance ? (
        <span className="ns-badge ns-badge-clearance">Clearance</span>
      ) : null}
    </div>
  );
}
Revived Prepackgenerated renderer, trimmed
500/500 byte-identical to React
… 17 lines omitted …
var _1 = function (data) {
  var _C = function (_$4, _$5) {
    "use strict";

    var _$6 = _$4.sku;
    var _$7 = _$4.stock;
    var _$8 = _$4.title;
    var _$9 = _$4.stock;
    var _$A = _$4.shipsAtSec;
    var _$B = _$4.originalPrice;
… 93 lines omitted …
    var _3G = 1 + (_3J - _$S);

    var _3t = 400 * _$K + _$O;

    return "<li class" + (!(_$7 <= 0) ? "=\"ns-card\"" : "=\"ns-card ns-card-out\"") + ">" + (!_Y ? "" : "<div class=\"ns-badges\">" + ((!(void 0 === _$G ? void 0 : _$G) ? "" : "<span class=\"ns-badge ns-badge-featured\">Featured</span>") + (void 0 === _$B || !_f ? "" : "<span class=\"ns-badge ns-badge-sale\">Sale</span>") + (!(void 0 === _$H ? void 0 : _$H) ? "" : "<span class=\"ns-badge ns-badge-clearance\">Clearance</span>")) + "</div>") + "<h3 class=\"ns-card-title\">" + _43(_$8) + "</h3>" + (_p ? "<span class=\"ns-stock ns-stock-out\" data-stock-state=\"out\">Out of stock</span>" : _t ? "<span class=\"ns-stock ns-stock-low\" data-stock-state=\"low\">Only <!-- -->" + _$9 + "<!-- --> left</span>" : "<span class=\"ns-stock ns-stock-in\" data-stock-state=\"in\">In stock" + (void 0 === _$A ? "" : "<!-- --> \xB7 Ships " + (_43(_$U) + (!(_3G < 10) ? "" + _3G : "0" + _3G)) + ", " + ((_1X ? _1S <= 2 : _1V <= 2) ? 1 + _3t : _3t)) + "</span>") + "</li>";
  };

  var _$0 = data.products;

  var _$3 = __ssrMapJoin(_$0, _C);

  var _$V = _$3;
  return "<ul class=\"ns-grid\">" + _$V + "</ul>";
};
… 20 lines omitted …
var _43 = function (value) {
  var str = "" + value;
  return _4c.test(str) ? str.replace(_4d, _4l) : str;
};
… 249 lines omitted …
408 lines · 14,568 bytes in total, including the schema check · compiled in 1.4 s

Context and useMemo

Contexts are pure plumbing at render time, so the providers and useContext calls fold away entirely: the locale becomes a plain read of data.locale. useMemo runs once per server render, so its body is specialized in place. The loop that totals the cart becomes a residual loop over the items, and the digit-grouping loop inside formatMoney stays as a small residual function. Everything else is arithmetic and string building.

The cart summary: two contexts, a useMemo over the cart, locale-aware money formatting and a list of line items. Checked against renderToString on the benchmark’s 500 check inputs: 500/500 byte-identical, 0 fallbacks. This component alone renders 4.5× faster than renderToString (1.9× including the per-request schema check).
InputCartSummaryAside, CartLineItem
function App(props) {
  var data = props.data;
  return (
    <LocaleContext.Provider value={data.locale}>
      <UserContext.Provider value={data.user}>
        <CartSummaryAside cart={data.cart} />
      </UserContext.Provider>
    </LocaleContext.Provider>
  );
}

function CartSummaryAside(props) {
  var cart = props.cart;
  var locale = React.useContext(LocaleContext);
  var user = React.useContext(UserContext);

  var totals = React.useMemo(
    function () {
      var subtotal = 0;
      var unitCount = 0;
      for (var i = 0; i < cart.items.length; i++) {
        var it = cart.items[i];
        subtotal += it.qty * it.unitPrice;
        unitCount += it.qty;
      }
      var discountRate =
        user && user.tier === "vip"
          ? 0.1
          : user && user.tier === "pro"
          ? 0.05
          : 0;
      var memberDiscount = subtotal > 0 ? -(subtotal * discountRate) : 0;
      var afterDiscount = subtotal + memberDiscount + cart.creditAdjustment;
      var taxable = afterDiscount > 0 ? afterDiscount : 0;
      var shipping =
        unitCount === 0 || taxable >= cart.freeShippingThreshold ? 0 : 14.95;
      var tax = taxable * cart.taxRate;
      var grandTotal = afterDiscount + shipping + tax;
      return {
        subtotal: subtotal,
        unitCount: unitCount,
        memberDiscount: memberDiscount,
        shipping: shipping,
        tax: tax,
        grandTotal: grandTotal,
      };
    },
    [cart, user]
  );

  var isEmpty = cart.items.length === 0;
  var showBreakdown = !isEmpty || cart.creditAdjustment !== 0;
  return (
    <aside className="ns-cart" data-empty={isEmpty}>
      <h2 className="ns-cart-title">Order Quote ({totals.unitCount})</h2>
      {isEmpty ? (
        <p className="ns-cart-empty">Your quote list is empty.</p>
      ) : (
        <ul className="ns-cart-list">
          {cart.items.map(function (it) {
            return <CartLineItem key={it.sku} item={it} />;
          })}
        </ul>
      )}
      {showBreakdown ? (
        <dl className="ns-cart-totals">
          <dt>Subtotal</dt>
          <dd>{formatMoney(totals.subtotal, locale)}</dd>
          {totals.memberDiscount !== 0 ? (
            <>
              <dt>Member Discount</dt>
              <dd>{formatMoney(totals.memberDiscount, locale)}</dd>
            </>
          ) : null}
          {cart.creditAdjustment !== 0 ? (
            <>
              <dt>Account Credit</dt>
              <dd>{formatMoney(cart.creditAdjustment, locale)}</dd>
            </>
          ) : null}
          <dt>Shipping</dt>
          <dd>
            {totals.shipping === 0
              ? "FREE"
              : formatMoney(totals.shipping, locale)}
          </dd>
          <dt>Est. Tax</dt>
          <dd>{formatMoney(totals.tax, locale)}</dd>
          <dt className="ns-cart-grand-label">Total</dt>
          <dd className="ns-cart-grand-val">
            {formatMoney(totals.grandTotal, locale)}
          </dd>
        </dl>
      ) : null}
      <details className="ns-cart-notes" open={Boolean(cart.notesOpen)}>
        <summary>Freight &amp; Net-30 Terms</summary>
        <p>Orders above {formatMoney(cart.freeShippingThreshold, locale)} ship free.</p>
      </details>
      <ActionButton variant="primary" disabled={isEmpty}>
        Proceed to Checkout
      </ActionButton>
    </aside>
  );
}

function CartLineItem(props) {
  var item = props.item;
  var locale = React.useContext(LocaleContext);
  var lineTotal = item.qty * item.unitPrice;
  return (
    <li className="ns-cart-item" data-sku={item.sku}>
      <div className="ns-cart-item-main">
        <span className="ns-cart-item-title">{item.title}</span>
        {item.backordered ? (
          <span className="ns-cart-backorder">Backorder</span>
        ) : null}
      </div>
      <div className="ns-cart-item-math">
        {item.qty} × {formatMoney(item.unitPrice, locale)} ={" "}
        <strong>{formatMoney(lineTotal, locale)}</strong>
      </div>
    </li>
  );
}
Revived Prepackgenerated renderer, trimmed
500/500 byte-identical to React
… 14 lines omitted …
var _1 = function (data) {
  var __l0, __l4, __l5, __lV, __lZ, __la, __l1, __l2, __l3, __l6, __lW, __lX, __lY, __lb, __l7, __l8, __l9, __lc, __ld, __le, __lA, __lB, __lC, __lf, __lg, __lh, __lD, __lE, __lF, __li, __lj, __lk, __lG, __lH, __lI, __ll, __lm, __ln, __lJ, __lK, __lL, __lo, __lp, __lq, __lM, __lN, __lO, __lr, __ls, __lt;

  var _A7 = function (_$4V, _$4W) {
… 172 lines omitted …
  return "<li class=\"ns-cart-item\" data-sku=\"" + _9c(_$4a) + "\"><div class=\"ns-cart-item-main\"><span class=\"ns-cart-item-title\">" + _9c(_$4b) + "</span>" + (!(void 0 === _$4c ? void 0 : _$4c) ? "" : "<span class=\"ns-cart-backorder\">Backorder</span>") + "</div><div class=\"ns-cart-item-math\">" + _$4d + "<!-- --> \xD7 " + (!(_$4e < 0) || !(_$4n > 0) ? _CV ? "" !== _$5A ? "<!-- -->" + _CI : "" : "" !== _$59 ? "<!-- -->" + _CQ : "" : "<!-- -->-" + (_CV ? _CI : _CQ)) + "<!-- --> =<!-- --> <strong>" + (!(_Cx < 0) || !(_$5J > 0) ? _Cm : "-" + _Cm) + "</strong></div></li>";
};
… 86 lines omitted …
var _$0 = data.locale;
var _$1 = data.user;
var _$2 = data.cart;
__lb = 0;
__lW = void 0;
__lX = 0;
__lY = 0;

_9S(_$2);

var _$L = __lb;
var _$M = __lW;
var _$N = __lX;
var _$O = __lY;

var _B = _$1 !== null;

var _E = _$1;

if (_B) {
  var _$P = _E.tier;
}

var _H = "vip" === _$P;

var _G = _B && _H;

if (!_G) {
  if (_B) {
    var _$Q = _E.tier;
  }
}

var _$R = _$N > 0;

if (_$R) {
  var _$S = _$N * (_G ? 0.1 : _B && "pro" === _$Q ? 0.05 : 0);
}

var _Y = -_$S;

var _X = _$R ? _Y : 0;

var _$T = _$N + _X;

var _$U = _$2.creditAdjustment;

var _$V = _$T + _$U;
… 496 lines omitted …
  var _$4R = _9a(_$O, true);

  var _$4S = _9b(_$O, true);

  if (_x) {
    var _$4U = __ssrMapJoin(_$g, _A7);

    var _$5h = _$4U;
  }

  var _$5i = _$5j.Boolean(_11);

  var _6O = !_1Y ? _9c(_$1E) : _9c(_$1D);

  var _6l = !_2B ? _9c(_$1k) : _9c(_$1j);

  var _7C = !_2r ? _9c(_$2I) : _9c(_$2H);

  var _7p = !_43 ? _9c(_$3K) : _9c(_$3J);

  var _8A = !_4g ? _9c(_$3r) : _9c(_$3q);

  var _93 = !_5J;

  var _8q = _9c(_$4Q);

  var _8y = _9c(_$4P);

  return "<aside class=\"ns-cart\" data-empty=\"" + _11 + "\"><h2 class=\"ns-cart-title\">Order Quote (" + _$4R + (_$4S ? "<!-- -->)" : ")") + "</h2>" + (_x ? "<ul class=\"ns-cart-list\">" + _$5h + "</ul>" : "<p class=\"ns-cart-empty\">Your quote list is empty.</p>") + (_11 && 0 === _$f ? "" : "<dl class=\"ns-cart-totals\"><dt>Subtotal</dt><dd>" + (!_$q || !(_$r > 0) ? _6O : "-" + _6O) + "</dd>" + (0 === _X ? "" : "<dt>Member Discount</dt><dd>" + (!_$R || !(_Y < 0) || !(_$1N > 0) ? _6l : "-" + _6l) + "</dd>") + (0 === _$1l ? "" : "<dt>Account Credit</dt><dd>" + (!(_$1m < 0) || !(_$1v > 0) ? _7C : "-" + _7C) + "</dd>") + "<dt>Shipping</dt><dd>" + (0 !== _$O && !_$Y ? !_3S ? _9c(_$2o) : _9c(_$2n) : "FREE") + "</dd><dt>Est. Tax</dt><dd>" + (!(_$a < 0) || !(_$2x > 0) ? _7p : "-" + _7p) + "</dd><dt class=\"ns-cart-grand-label\">Total</dt><dd class=\"ns-cart-grand-val\">" + (!_$3T || !(_$3U > 0) ? _8A : "-" + _8A) + "</dd></dl>") + "<details class=\"ns-cart-notes\"" + (!_$3t ? "" : " open=\"\"") + "><summary>Freight &amp; Net-30 Terms</summary><p>Orders above " + (!(_$3u < 0) || !(_$43 > 0) ? _93 ? "" !== _$4Q ? "<!-- -->" + _8q : "" : "" !== _$4P ? "<!-- -->" + _8y : "" : "<!-- -->-" + (_93 ? _8q : _8y)) + "<!-- --> ship free.</p></details><button type=\"button\" class=\"ns-btn ns-btn-primary\"" + (!_$5i ? "" : " disabled=\"\"") + " data-variant=\"primary\">Proceed to Checkout</button></aside>";
};
… 290 lines omitted …
1,143 lines · 28,654 bytes in total, including the schema check · compiled in 1.2 s

useId inside nested lists

React 18’s ids depend on a component’s position in the tree, and they have to match React’s format exactly. Outside a list the position is known at build time, so :R0: and :R0H1: are folded straight into the HTML. Inside a list of unknown length it isn’t, so the generated code carries React’s tree-id arithmetic and computes each option’s :R…: id at runtime. Boolean attributes (disabled, hidden, and checked from defaultChecked) become conditional pieces of the attribute string.

The filter sidebar: groups of checkboxes, each with a useId label, nested in two lists of unknown length. Checked against renderToString on the benchmark’s 500 check inputs: 500/500 byte-identical, 0 fallbacks. This component alone renders 9.9× faster than renderToString (2.2× including the per-request schema check).
InputFilterSidebar, FilterGroup, FilterOption
function App(props) {
  var data = props.data;
  return (
    <LocaleContext.Provider value={data.locale}>
      <FilterSidebar filters={data.filters} inStockOnly={data.search.inStockOnly} />
    </LocaleContext.Provider>
  );
}

function FilterSidebar(props) {
  var headingId = React.useId();
  var stockToggleId = React.useId();
  var filters = props.filters;
  return (
    <aside className="ns-sidebar" aria-labelledby={headingId}>
      <h2 id={headingId} className="ns-sidebar-title">
        Filters
      </h2>
      <div className="ns-stock-toggle">
        <input
          type="checkbox"
          id={stockToggleId}
          name="in_stock"
          defaultChecked={Boolean(props.inStockOnly)}
        />
        <label htmlFor={stockToggleId}>In-stock items only</label>
      </div>
      {filters.map(function (group) {
        return <FilterGroup key={group.id} group={group} />;
      })}
    </aside>
  );
}

function FilterGroup(props) {
  var group = props.group;
  return (
    <fieldset
      className="ns-filter-group"
      disabled={Boolean(group.disabled)}
      data-filter-key={group.id}
    >
      <legend className="ns-filter-legend">{group.title}</legend>
      {group.options.length === 0 ? (
        <p className="ns-filter-empty">No options</p>
      ) : (
        <ul className="ns-filter-list">
          {group.options.map(function (opt) {
            return (
              <FilterOption
                key={opt.value}
                groupId={group.id}
                groupDisabled={group.disabled}
                option={opt}
              />
            );
          })}
        </ul>
      )}
    </fieldset>
  );
}

function FilterOption(props) {
  var opt = props.option;
  var optId = React.useId();
  var disabled = Boolean(props.groupDisabled || opt.count === 0);
  return (
    <li className={cx("ns-filter-opt", { "is-selected": opt.selected })}>
      <input
        type="checkbox"
        id={optId}
        name={"f_" + props.groupId}
        value={opt.value}
        defaultChecked={Boolean(opt.selected)}
        disabled={disabled}
      />
      <label htmlFor={optId}>{opt.label}</label>
      <CountPill count={opt.count} hot={false} />
    </li>
  );
}
Revived Prepackgenerated renderer, trimmed
500/500 byte-identical to React
… 14 lines omitted …
var _1 = function (data) {
  var _Y = function (_$8, _$9) {
    "use strict";

    var _1W = function (_$L, _$M) {
      "use strict";

      var _$N = _$L.value;
… 83 lines omitted …
      var _2w = _4d(15, _$6, _$9);

      var _2v = _4d(_2w, 2, 1);

      var _2m = "=\":R" + _1L(_4c(_4d(_2v, _$J, _$M), _4e(_2v, _4e(_2w, _4e(15, "", _$6), 2), _$J))) + ":\"";

      var _3n = "<span class=\"ns-pill\"" + (!(_$X <= 0) ? "" : " hidden=\"\"") + " data-zero=\"" + (0 === _$X) + "\">";

      var _4J = _2I && _$11 == null;

      var _4H = _1L(_$12);

      return "<li class" + (!_$S ? "=\"ns-filter-opt\"" : "=\"ns-filter-opt is-selected\"") + "><input type=\"checkbox\" id" + _2m + " name=\"f_" + _1L(_$O) + "\"" + (!_$R ? "" : " disabled=\"\"") + (!_$V ? "" : " checked=\"\"") + " value=\"" + _1L(_$T) + "\"/><label for" + _2m + ">" + _1L(_$W) + "</label>" + ((_2Q ? _3n : _3n + (_4J ? "null" : _2F ? _2I ? "boolean" !== _2M && _2M !== "number" ? _1L(_$11) : _$14 : _4H : _$13)) + (_1x ? "" : _4J ? "" : _2I ? _$15 : "" !== _$12 ? _4H : "")) + "</span></li>";
    };

    var _$A = _$8.id;
    var _$B = _$8.disabled;

    var _$C = _$19.Boolean(void 0 === _$B ? void 0 : _$B);

    var _$D = _$8.id;
    var _$E = _$8.title;
    var _$F = _$8.options;
    var _$G = _$F.length;

    if (!(0 === _$G)) {
      var _$H = _$8.options;
    }

    var _i = 0 !== _$G;

    if (_i) {
      var _1S = _$H;
      var _$J = _1S.length;

      var _$K = __ssrMapJoin(_1S, _1W);

      var _$17 = _$K;
    }

    return "<fieldset class=\"ns-filter-group\"" + (!_$C ? "" : " disabled=\"\"") + " data-filter-key=\"" + _1L(_$D) + "\"><legend class=\"ns-filter-legend\">" + _1L(_$E) + "</legend>" + (_i ? "<ul class=\"ns-filter-list\">" + _$17 + "</ul>" : "<p class=\"ns-filter-empty\">No options</p>") + "</fieldset>";
  };

  var _$0 = data.locale;
  var _$1 = data.filters;
  var _$2 = data.search;
  var _$3 = _$2.inStockOnly;

  var _$4 = _$19.Boolean(_$3);

  var _W = _$1;
  var _$6 = _W.length;

  var _$7 = __ssrMapJoin(_W, _Y);

  var _$18 = _$7;
  return "<aside class=\"ns-sidebar\" aria-labelledby=\":R0:\"><h2 id=\":R0:\" class=\"ns-sidebar-title\">Filters</h2><div class=\"ns-stock-toggle\"><input type=\"checkbox\" id=\":R0H1:\" name=\"in_stock\"" + (!_$4 ? "" : " checked=\"\"") + "/><label for=\":R0H1:\">In-stock items only</label></div>" + _$18 + "</aside>";
};
… 345 lines omitted …
509 lines · 17,280 bytes in total, including the schema check · compiled in 0.5 s

Fragments, text separators and raw HTML

dangerouslySetInnerHTML is inserted as is, without escaping. The keyed fragments in the breadcrumb list flatten into adjacent <li> elements, and the {" "}(Min. order …) text run gets the <!-- --> separators React emits between adjacent text nodes. A null promo removes the whole section.

An announcement banner with raw HTML and a minimum-order note, and a breadcrumb list built from keyed fragments. Checked against renderToString on the benchmark’s 500 check inputs: 500/500 byte-identical, 0 fallbacks. This component alone renders 9.4× faster than renderToString (1.2× including the per-request schema check).
InputAnnouncementBanner, Breadcrumbs
function App(props) {
  var data = props.data;
  return (
    <LocaleContext.Provider value={data.locale}>
      <AnnouncementBanner promo={data.promo} />
      <Breadcrumbs breadcrumbs={data.breadcrumbs} />
    </LocaleContext.Provider>
  );
}

function AnnouncementBanner(props) {
  var locale = React.useContext(LocaleContext);
  var promo = props.promo;
  if (!promo) {
    return null;
  }
  return (
    <section
      className={cx("ns-promo", { "ns-promo-urgent": Boolean(promo.urgent) })}
      hidden={!promo.active}
      data-campaign={promo.code}
    >
      <span
        className="ns-promo-html"
        dangerouslySetInnerHTML={{ __html: promo.html }}
      />
      {promo.minOrder !== undefined && promo.minOrder > 0 ? (
        <span className="ns-promo-min">
          {" "}
          (Min. order {formatMoney(promo.minOrder, locale)})
        </span>
      ) : null}
      <code className="ns-promo-code">{promo.code}</code>
    </section>
  );
}

function Breadcrumbs(props) {
  var crumbs = props.breadcrumbs;
  if (!crumbs || crumbs.length === 0) {
    return null;
  }
  return (
    <nav className="ns-crumbs" aria-label="Breadcrumb">
      <ol className="ns-crumb-list">
        {crumbs.map(function (crumb, idx) {
          var isLast = idx === crumbs.length - 1;
          return (
            <React.Fragment key={crumb.href + ":" + idx}>
              {idx > 0 ? (
                <li className="ns-crumb-sep" aria-hidden="true">
                  /
                </li>
              ) : null}
              <li className={cx("ns-crumb-item", { "is-current": isLast })}>
                {isLast ? (
                  <span aria-current="page">{crumb.label}</span>
                ) : (
                  <a href={crumb.href}>{crumb.label}</a>
                )}
              </li>
            </React.Fragment>
          );
        })}
      </ol>
    </nav>
  );
}
Revived Prepackgenerated renderer, trimmed
500/500 byte-identical to React
… 17 lines omitted …
var _2z = function (_$m, _$n) {
  "use strict";

  var _$o = _2r.length;
  var _$p = _$m.href;

  var _34 = _$o - 1;

  if (_34 === _$n) {
    var _$q = _$m.label;
  } else {
    var _$r = _$m.href;
    var _$s = _$m.label;
  }

  var _3P = _34 !== _$n;

  return (_$n <= 0 ? "" : "<li class=\"ns-crumb-sep\" aria-hidden=\"true\">/</li>") + "<li class" + (_3P ? "=\"ns-crumb-item\"" : "=\"ns-crumb-item is-current\"") + ">" + (_3P ? "<a href=\"" + _2q(_$r) + "\">" + _2q(_$s) + "</a>" : "<span aria-current=\"page\">" + _2q(_$q) + "</span>") + "</li>";
};
… 12 lines omitted …
var _$0 = data.locale;
var _$1 = data.promo;
var _$2 = data.breadcrumbs;

if (_$1 !== null) {
  var _8 = _$1;
  var _$3 = _8.urgent;

  var _$4 = _$u.Boolean(_$3);

  var _$5 = _8.active;
  var _$6 = _8.code;
  var _$7 = _8.html;
  var _$8 = _8.minOrder;
… 80 lines omitted …
    var _$h = _8.code;
  }

  var _2r = _$2;
  var _$i = _2r.length;

  var _17 = 0 !== _$i;

  if (_17) {
    var _$l = __ssrMapJoin(_2r, _2z);

    var _$t = _$l;
  }

  var _2C = !_v;

  var _1z = _2q(_$g);

  var _27 = _2q(_$f);

  return (_$1 === null ? "" : "<section class" + (!_$4 ? "=\"ns-promo\"" : "=\"ns-promo ns-promo-urgent\"") + (_$5 ? "" : " hidden=\"\"") + " data-campaign=\"" + _2q(_$6) + "\"><span class=\"ns-promo-html\">" + _$7 + "</span>" + (void 0 === _$8 || void 0 === _$9 || !_M ? "" : "<span class=\"ns-promo-min\"> <!-- -->(Min. order " + (_d || !(_b < 0) || !(_$J > 0) ? _2C ? "" !== _$g ? "<!-- -->" + _1z : "" : "" !== _$f ? "<!-- -->" + _27 : "" : "<!-- -->-" + (_2C ? _1z : _27)) + "<!-- -->)</span>") + "<code class=\"ns-promo-code\">" + _2q(_$h) + "</code></section>") + (0 === _$i ? "" : "<nav class=\"ns-crumbs\" aria-label=\"Breadcrumb\"><ol class=\"ns-crumb-list\">" + _$t + "</ol></nav>");
};
… 273 lines omitted …
438 lines · 14,455 bytes in total, including the schema check · compiled in 1.0 s

The per-component timings are in-process, warm, on a shared machine, and the schema check validates the whole request on every call, which dominates for one small component. The page-level numbers below are the meaningful ones. On the full page, over the benchmark’s fixed 500-input check corpus, the output is byte-identical to ReactDOMServer.renderToString (re-checked for this article: 500/500). It renders 4–5.5× faster than React with the JIT and about 9–10× faster with --jitless, within 1.4–2× of a page specialized by hand. Data that doesn’t match the inferred schema falls back to React.

Renders per second relative to ReactDOMServer.renderToString, same machine, same run (react-bench/perf.js, November 5, 2026). Small pages are about 6 KB of HTML, large ones about 285 KB.

JIT, smallJIT, largejitless, smalljitless, large
React renderToString1.00×1.00×1.00×1.00×
Revived Prepack5.39×4.10×8.96×10.08×
Hand-written specialization8.42×6.51×18.53×17.45×

The generated program is 83.8 KB (20.6 KB gzipped) and doesn’t load React at all. The Northstar Supply demo serves the same page rendered by plain React and by the specialized code.

What still breaks

Two app shapes still produce broken output; both turned up while building the Northstar store. One is a list reached through an object that may be null (listing.products, where listing is either null or an object). The other is a loop and a .map over the same array inside one conditional branch. In both cases Prepack doesn’t recognize the array as a list of unknown length, falls back to a generic path that is broken, and the build still succeeds. The generated code contains a residual __bailout call and throws on the requests that render the list. It never returns wrong HTML, but the compiler should refuse instead of shipping code that throws. The Northstar store works around both by keeping its collections at the top level of the data and computing listing stats outside the view branch.

Both shapes, compiled from 200 synthetic training requests and run on 200 more (site/scripts/run-react-examples.js).
Inputa list behind a nullable object
Builds, then throws on 134 of 200 requests (_4Z is not a function); 66 render correctly
function Grid(props) {
  return <ul className="grid">{props.products.map(function (p) {
    return <li key={p.sku}>{p.title}{p.stock > 0 ? " in stock" : null}</li>;
  })}</ul>;
}
function Listing(props) {
  var inStock = React.useMemo(function () {
    var n = 0;
    for (var i = 0; i < props.listing.products.length; i++) if (props.listing.products[i].stock > 0) n++;
    return n;
  }, [props.listing]);
  return <section><h1>{props.listing.title}</h1><p>{inStock} in stock</p><Grid products={props.listing.products} /></section>;
}
function App(props) {
  var listing = props.data.listing; // null, or { title, products: [...] }
  return <main>{listing ? <Listing listing={listing} /> : <p>No listing</p>}</main>;
}
Inputa loop and a map in one branch
Builds, then throws on 96 of 200 requests (_3S is not a function); 104 render correctly
function Grid(props) {
  return <ul className="grid">{props.products.map(function (p) {
    return <li key={p.sku}>{p.title}</li>;
  })}</ul>;
}
function ListingPage(props) {
  var inStock = React.useMemo(function () {
    var n = 0;
    for (var i = 0; i < props.products.length; i++) if (props.products[i].stock > 0) n++;
    return n;
  }, [props.products]);
  return <section><p>{inStock} in stock</p><Grid products={props.products} /></section>;
}
function App(props) {
  var data = props.data;
  return <main>{data.view === "listing" ? <ListingPage products={data.products} /> : <p>Home</p>}</main>;
}

Part 3

What we learned

What is solved, what isn’t, and when the revived Prepack is the right tool.

Most of the problems that stopped Prepack turned out to be solvable, in the sense that matters: the cases from the tracker now compile to correct, compact code, and the ones that don’t stop with a diagnostic instead of shipping something wrong. None of them was solved by one clever idea. Each was a few thousand lines of careful work, checked by a corpus, a fuzzer and a solver.

ProblemVerdictWhat that means
P1 LoopsSolved for local effectsResidual loops with a one-iteration soundness check. Loops whose effects reach global or outer state still stop with a diagnostic: summarizing those needs whole-program reasoning, and loop behavior is undecidable in general.
P2 RecursionSolvedResidual calls once recursion depends on unknown values, with effect checks; refused (PP0050) when it can’t be proven safe.
P3 JoinsSolvedEvery known miscompile fixed; switch and residual try/catch implemented. The completion design is still fragile; a simpler representation is sketched.
P4 Path conditionsSolved with Z3Solver-backed implications, a rewrite audit with zero failures, and documented limits (no IEEE-754 rounding, opaque string conversions).
P5 Numeric factsSolved for small rangesInterval domain plus case splitting up to 8 values; wide or unbounded lengths still need relational summaries.
P6 HavocMostly81 built-in summaries and the outer fixed point. Aliasing between unknown parameters and untyped receivers remain conservative.
P7 EnvironmentSolved, by making it explicitOpen-world globals, generated models, guarded assumptions; zero silent miscompiles on 22 libraries × 6 profiles.
P8 SizeSolved on the corpusCost model and peepholes; no case above 2×. The UMD three-way split can still double a library when no target is given.
P9 Generators and asyncBy choice, partlyGenerators finished at init and settled Promises work; a generator suspended at the end of init is refused rather than approximated.

What isn’t solved

Loops and recursion are undecidable in general, and the revived Prepack doesn’t pretend otherwise. It handles the shapes whose effects it can bound and refuses the rest. Modern libraries still expose gaps in its 35,000-line spec implementation at a steady rate, and compile times on large inputs are long (279 seconds for @babel/standalone). Without a target environment, a UMD bundle is evaluated once per module system, which can double the output. And the size problem is only solved in the sense Markbåge meant if output never grows: on our corpus it doesn’t, but a compiler that evaluates code will always be able to produce more code than it was given. The cost model only decides when that is worth it.

When it is the right tool

Specializing on unknown data

A renderer for a known app, an optimized function for unknown arguments, an init path that depends on an environment you can describe. This is where symbolic evaluation earns its keep, and where the revived Prepack is strongest.

Init-heavy bundles with a code cache

Libraries that compute tables, parse locale data or register plugins at load start several times faster from V8’s code cache (moment 4.3×, handlebars 3.1×), usually at about the original size.

Not when the engine can do it

Node’s and V8’s own startup snapshots remain the cheapest way to skip initialization where they’re available, and they were the first thing our initial research found still alive from Prepack’s idea.

Herman Venter wrote in 2018 that getting Prepack to run on real browser applications would take “a lot of very hard work and a long time,” and that shortcuts would produce something that works “only for some ill defined subset of JavaScript.” Both halves held up. What made the hard work tractable this time was checking it: a corpus built from the tracker’s own hard cases, a differential fuzzer, and a solver that audits every rewrite. The final count is 190 of 194 hard cases, with zero silently wrong.

Appendix

Methods and the full scoreboard

How every number and output on this page was produced, and all 194 hard cases.

How this page was made

Every code panel shows the unedited output of a real tool run, produced by site/scripts/run-examples.js from the inputs in site/examples/. Long outputs are trimmed only where a panel says so, and long lines in the two React panels wrap; the Prepack fragment there also has line breaks added at ? and :. The script then runs the original program and each output in a fresh Node process and evaluates the expression shown in the “Run” table, so every “differs” mark is measured.

All 194 hard cases

Folder names give the problem (P1–P9), fuzz/ holds minimized fuzzer findings, and the real-world folders hold library excerpts. Hover a status for the scoreboard’s detail; the last column is 0.2.54’s first diagnostic or mismatch.

Show the table (194 rows)
Case0.2.54Revived0.2.54 detail
P1-loops/collatz-data-dependent.jsErrorPassFatalError PP0001:
P1-loops/do-while-build-object.jsWrongPassexpected "[{\"first\":true,\"k0\":0,\"count\":1},{\"first\":true,\"k0\":0,\"k1\":1,\"k2\":4,\"count\":3},{\"first\":true,\"k0\":0,\"k1\":1,\"k2\":4,\"k3\":9,\"k
P1-loops/for-in-unknown-object.jsErrorPassFatalError PP0013: for in loops over unknown objects are not yet supported
P1-loops/gcd-while-two-vars.jsErrorPassFatalError PP0001:
P1-loops/issue-1174-global-unknown-bound.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value (7)
P1-loops/issue-1174-unknown-bound-counter.jsErrorPassFatalError PP0001:
P1-loops/issue-1722-state-before-loop.jsWrongPassexpected "{\"0\":\"a\",\"1\":\"b\",\"2\":\"x\",\"3\":\"x\",\"4\":\"x\"} i=5" got "{\"3\":\"x\",\"4\":\"x\",\"5\":\"x\"} i=5"
P1-loops/issue-1959-shallow-equal.jsErrorPassFatalError PP0001: ; FatalError PP0037: failed to recover from a for/while loop bail-out due to unsupported logic in loop body
P1-loops/issue-2053-babel-for-of.jsErrorPassFatalError PP0001: ; FatalError PP0035: break, throw or return cannot be guarded by abstract condition
P1-loops/issue-2055-while-string-search.jsErrorPassFatalError PP0001:
P1-loops/issue-2396-while-pure.jsErrorPassFatalError PP0001:
P1-loops/issue-2396-while-return.jsErrorPassFatalError PP0001:
P1-loops/issue-2477-babel-for-of-switch-return.jsErrorPassFatalError PP0001: (x2); FatalError PP0037: failed to recover from a for/while loop bail-out due to unsupported logic in loop body
P1-loops/labeled-continue-nested.jsErrorPassFatalError PP0001: ; FatalError PP0037: failed to recover from a for/while loop bail-out due to unsupported logic in loop body
P1-loops/loop-break-in-optimize.jsErrorPassFatalError PP0001:
P1-loops/loop-return-in-optimize.jsErrorPassFatalError PP0001: ; FatalError PP0037: failed to recover from a for/while loop bail-out due to unsupported logic in loop body
P1-loops/loop-string-accumulator.jsErrorPassFatalError PP0001:
P1-loops/loop-throw-in-optimize.jsErrorPassFatalError PP0001: ; FatalError PP0037: failed to recover from a for/while loop bail-out due to unsupported logic in loop body
P1-loops/loop-writes-known-object-unknown-keys.jsErrorPassFatalError PP0001:
P1-loops/nested-loops-unknown-bounds.jsErrorPassFatalError PP0001:
P1-loops/while-string-trim-global.jsErrorPassRecoverableError PP0030: unknown property access might need to invoke a getter; FatalError PP0001: This operation is not yet supported on abstract values ("hell
P2-recursion/concrete-recursion-under-abstract-cond.jsPassPass
P2-recursion/deep-clone.jsErrorPassFatalError PP0001:
P2-recursion/descent-parser.jsErrorPassFatalError PP0045: Maximum stack depth exceeded (x2); FatalError PP0001: (x74)
P2-recursion/factorial-under-condition.jsErrorPassFatalError PP0045: Maximum stack depth exceeded
P2-recursion/fib-optimized.jsErrorPasstimeout after 30000ms
P2-recursion/flatten-recursive.jsErrorPassFatalError PP0001:
P2-recursion/gcd-recursive.jsErrorPasstimeout after 30000ms
P2-recursion/issue-1586-fibonacci-abstract.jsErrorPassFatalError PP0045: Maximum stack depth exceeded
P2-recursion/issue-543-fib-date-now.jsPassPass
P2-recursion/mutual-recursion-even-odd.jsErrorPassFatalError PP0045: Maximum stack depth exceeded
P2-recursion/recursive-path-lookup.jsErrorPassFatalError PP0045: Maximum stack depth exceeded
P2-recursion/tree-depth.jsErrorPassFatalError PP0001:
P2-recursion/tree-walk-sum.jsErrorPasstimeout after 30000ms
P3-joins/catch-rethrow-abstract.jsErrorPassRecoverableError PP0021: Possible throw inside try/catch is not yet supported (x2)
P3-joins/fuzz-nested-conditional-assign-return.jsPassPass
P3-joins/fuzz-returns-counters.jsPassPass
P3-joins/global-conditional-throw-then-work.jsToo largePassoutput 1169B is 3.39x input 345B (max 2x)
P3-joins/issue-1378-switch-abstract.jsPassPass
P3-joins/issue-1378-switch-fallthrough.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value
P3-joins/issue-1869-duplicate-console-log.jsPassPass
P3-joins/issue-2120-havoced-object-access-throws.jsWrongPassexpected "7,42,42" got "Error: no"
P3-joins/issue-2120-try-catch-abstract-call.jsErrorPassRecoverableError PP0021: Possible throw inside try/catch is not yet supported
P3-joins/issue-2354-multiple-returns.jsWrongPassexpected "false,true,false,true,false,true,false,false|falsetrue" got "false,true,false,true,false,true,false,false|NaNx"
P3-joins/issue-2363-possibly-normal-completion.jsPassPass
P3-joins/issue-2363-with-values.jsPassPass
P3-joins/issue-2561-throw-before-constructor.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value (true)
P3-joins/labeled-block-break-global.jsPassPass
P3-joins/labeled-block-break.jsWrongPassexpected "start,start-1,start-1-2" got "start-1-2,start-1-2,start-1-2"
P3-joins/nested-returns-side-effects.jsErrorPassFatalError PP0001:
P3-joins/return-in-nested-function-conditional-objects.jsWrongPassexpected "[{\"kind\":\"A\"},{\"kind\":\"A\",\"touched\":true},{\"kind\":\"B\",\"touched\":true,\"extra\":1},{\"kind\":\"B\",\"touched\":true}]" got "[{\"kind\":
P3-joins/short-circuit-abstract-calls.jsPassPass
P3-joins/switch-global-abstract.jsPassPass
P3-joins/throw-in-constructor.jsErrorPassRecoverableError PP0021: Possible throw inside try/catch is not yet supported (x6)
P3-joins/try-finally-return-abstract.jsErrorPassRecoverableError PP0021: Possible throw inside try/catch is not yet supported (x2)
P4-paths/comparison-chain-numbers.jsToo largePassoutput 505B is 2.03x input 249B (max 2x)
P4-paths/de-morgan-mixed-types.jsPassPass
P4-paths/equality-refinement.jsPassPass
P4-paths/infeasible-branch-pruning.jsToo largePasscontains "unreachable"
P4-paths/issue-2327-simplify-binary.jsPassPass
P4-paths/issue-2355-truthiness-return.jsWrongPassexpected "true,false,false,true,false,false,false,true" got "1,0,,x,,,NaN,[object Object]"
P4-paths/issue-2361-pushing-false.jsPassPass
P4-paths/issue-2563-undefined-not-null.jsPassPass
P4-paths/issue-2564-null-not-undefined.jsPassPass
P4-paths/nested-ternary-falsy-values.jsErrorPassFatalError PP0001:
P4-paths/null-check-then-property.jsPassPass
P4-paths/repeated-condition-reuse.jsErrorPassFatalError PP0001:
P4-paths/truthiness-ternary-mixed.jsErrorPassFatalError PP0001:
P4-paths/typeof-refinement.jsToo largePassoutput 811B is 2.08x input 389B (max 2x)
P5-ranges/clamp-then-loop.jsErrorPassFatalError PP0001:
P5-ranges/conditional-index-write.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value
P5-ranges/conditional-pop-length-arith.jsErrorPassFatalError PP0001:
P5-ranges/conditional-push-global-many-unique-names.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value (true)
P5-ranges/conditional-push-global-many.jsErrorErrorFatalError PP0019: An abstract value with the same name exists
P5-ranges/conditional-push-last-element.jsErrorPassFatalError PP0001:
P5-ranges/conditional-push-optimized.jsErrorPassFatalError PP0001:
P5-ranges/issue-1066-conditional-push.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value (true)
P5-ranges/masked-index-lookup.jsToo largePassoutput 434B is 2.54x input 171B (max 2x)
P5-ranges/string-length-range.jsPassPass
P6-havoc/abstract-fn-leaks-global-object.jsErrorErrorRecoverableError PP0017: Unfrozen object leaked before end of global code
P6-havoc/aliasing-two-params.jsPassPass
P6-havoc/array-filter-callback-mutates-local.jsPassPass
P6-havoc/getter-on-abstract-object.jsPassPass
P6-havoc/issue-1750-lazy-cache.jsWrongPassexpected "a1,a2,b1,a3" got "a1,a1,b1,a1"
P6-havoc/issue-1750-lazy-singleton.jsWrongPassexpected "true:1:1" got "false:1:1"
P6-havoc/issue-2011-leaked-binding.jsPassPass
P6-havoc/issue-2012-havoc-binding-capture.jsToo largePassoutput 730B is 2.53x input 289B (max 2x)
P6-havoc/issue-2179-topval-alias.jsPassPass
P6-havoc/issue-2226-setter-on-prototype.jsToo largePassoutput 748B is 2.8x input 267B (max 2x)
P6-havoc/issue-2319-indexof.jsPassPass
P6-havoc/issue-2319-json-stringify-local.jsErrorPassFatalError PP0001:
P6-havoc/issue-2319-math-abs.jsErrorPassFatalError PP0001: (x7)
P6-havoc/issue-2319-parseint.jsErrorPassFatalError PP0001:
P6-havoc/issue-2319-regexp-test.jsErrorPassFatalError PP0001:
P6-havoc/issue-977-read-write-conflict.jsPassPass
P6-havoc/object-keys-loop-over-param.jsErrorPassFatalError PP0001:
P6-havoc/object-passed-to-unknown-fn.jsPassPass
P6-havoc/unknown-fn-mutates-captured-binding.jsPassPass
P7-environment/date-local-timezone.jsWrongPassexpected "1577817000000,-330" got "1577836800000,0"
P7-environment/date-now-typeof.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value
P7-environment/dom-build-tree-at-init.jsErrorPassFatalError PP0012: member expression object abstract value document is unknown
P7-environment/dom-get-element-by-id.jsErrorPassFatalError PP0012: member expression object abstract value document is unknown
P7-environment/guarded-speculation-fallback.jsWrongPassexpected "1577817000000,-330" got "1577836800000,0"
P7-environment/guarded-speculation-holds.jsWrongPassexpected "prod,1577817000000,-330" got "ReferenceError: process is not defined"
P7-environment/intl-number-format.jsWrongPassexpected "1.234,5" got "n/a"
P7-environment/local-storage-init.jsWrongPassexpected "5,0" got "ReferenceError: localStorage is not defined"
P7-environment/math-random-init.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value
P7-environment/navigator-user-agent.jsWrongPassexpected "{\"isMac\":true,\"isWindows\":false,\"lang\":\"en\"}Cmd" got "ReferenceError: navigator is not defined"
P7-environment/node-require-module.jsWrongPassexpected "src/lib/a/b" got "ReferenceError: require is not defined"
P7-environment/number-to-locale-string.jsErrorPassFatalError: TODO #1005: implement Date.prototype.toLocaleDateString
P7-environment/process-env-branch.jsWrongPassexpected "{\"minify\":true,\"logLevel\":\"debug\"}" got "ReferenceError: process is not defined"
P7-environment/react-dom-vendor-prefix-init.jsErrorPassFatalError PP0012: member expression object abstract value document is unknown
P7-environment/request-animation-frame.jsWrongPassexpected "frame@1620px" got "ReferenceError: requestAnimationFrame is not defined"
P7-environment/timers-set-interval-count.jsPassPass
P7-environment/timers-set-timeout-order.jsPassPass
P7-environment/umd-three-way-probe.jsWrongPassexpected "sum=60" got "TypeError: module.exports.format is not a function"
P7-environment/window-global-alias.jsWrongPassexpected "api:true" got "api:false"
P8-size/crc32-table.jsToo largePassoutput 3053B is 9.25x input 330B (max 2x)
P8-size/issue-2011-binding-writes.jsToo largePassoutput 326B is 1.83x input 178B (max 1.5x)
P8-size/issue-2140-inlining-explosion.jsErrorPassRecoverableError PP0008: might be a symbol or an object with an unknown valueOf or toString or Symbol.toPrimitive method (x8); FatalError PP0001:
P8-size/issue-2220-empty-if.jsToo largePasscontains "{}"
P8-size/issue-2286-cse-effectful.jsToo largePassoutput 236B is 1.58x input 149B (max 1.5x)
P8-size/repeated-defaults-objects.jsToo largePassoutput 1835B is 6.33x input 290B (max 2x)
P8-size/residual-closure-drags-code.jsPassPass
P8-size/string-builder-unrolled.jsToo largePassoutput 769B is 5.57x input 138B (max 2x)
P8-size/unrolled-table-1000.jsToo largePassoutput 12105B is 70.79x input 171B (max 2x)
P9-generators-async/async-function-defined-at-init.jsWrongPassexpected "object,function,object,function" got "number,undefined,number,undefined"
P9-generators-async/generator-function-defined-not-called.jsWrongPassexpected "0,1,2" got "SyntaxError: Unexpected identifier 'i'"
P9-generators-async/generator-suspended-at-init.jsErrorErrorInvariant Violation:
P9-generators-async/promise-then-at-init.jsWrongPassexpected "2,then-ok" got "pending,then-throws"
fuzz/array-abstract-index-set-length.jsWrongPassexpected "1,2,5,1" got "1,1,1,1"
fuzz/call-after-nested-returns.jsWrongPassexpected "0,0,0,0,undefined,undefined,undefined,0" got "0,0,0,0,0,0,undefined,0"
fuzz/composed-saved-completion-labeled-break.jsWrongPassexpected "done,0,throw:RangeError,throw:TypeError,done" got "throw:RangeError,0,throw:RangeError,throw:TypeError,done"
fuzz/conditional-call-base-nullish.jsWrongPassexpected "42,throw:TypeError,throw:TypeError" got "42,null,undefined"
fuzz/conditional-inner-throw-outer-alternate.jsWrongPassexpected "outer,outer,outer,outer,0,0,throw:boom" got "0,0,0,0,0,0,throw:boom"
fuzz/conditional-nested-temporal-scope.jsErrorPassFatalError PP0001:
fuzz/conditional-null-check-negation.jsWrongPassexpected "true,false,true,true,true,false,true,true|true,false,true,true,true,false,true,true" got "false,false,true,true,false,false,false,false|false,false,tr
fuzz/conditional-push-while-join-mismatch.jsErrorPassInvariant Violation: serialized 21 of 23
fuzz/conditional-unknown-write-empty-to-undefined.jsWrongPassexpected "[[[\"p1\"],true,\"undefined\"],[[\"length\",\"p1\"],true,\"undefined\"],[[\"q\",\"p1\"],true,\"undefined\"]]" got "[[[\"p1\"],true,\"undefined\"],[[\"
fuzz/dead-if-hoisted-var.jsWrongPassexpected "[\"undefined\",null,null]" got "ReferenceError: x is not defined"
fuzz/delete-abstract-coercible-base.jsErrorPassInvariant Violation:
fuzz/do-while-break-abrupt-widen.jsErrorPassInvariant Violation: if a loop iteration ends abruptly, there is no need for fixed point computation
fuzz/do-while-emitter-active-generator.jsErrorPassInvariant Violation:
fuzz/do-while-false-abrupt.jsWrongPassexpected "throw:body,early,throw:boom,no-throw,late,test-ok,throw:body,early,throw:boom,no-throw,late,test-ok" got "no-throw,late,test-ok,no-throw,late,test-ok,
fuzz/do-while-object-emitter.jsErrorPassInvariant Violation:
fuzz/do-while-object-hoisted-param.jsWrongPassexpected "42" got "ReferenceError: a is not defined"
fuzz/for-in-continue-return-join.jsWrongPassexpected "0,3,0,0,2" got "0,0,0,0,0"
fuzz/infeasible-path-explored-crash.jsErrorPassError: Infeasible path explored
fuzz/inlining-probe-nullish-arg.jsErrorPassFatalError PP0001: (x2)
fuzz/labeled-break-before-throw-call.jsErrorErrorInvariant Violation:
fuzz/labeled-break-skips-code-after-block.jsWrongPassexpected "called:true,called:false,called:1,called:0" got "undefined,called:false,undefined,called:0"
fuzz/leaked-binding-declared-across-passes.jsErrorPassRecoverableError PP0008: might be a symbol or an object with an unknown valueOf or toString or Symbol.toPrimitive method (x2); FatalError PP0001:
fuzz/leaked-object-in-operator.jsErrorPassFatalError PP0001:
fuzz/loop-null-check-path-condition.jsErrorPassInvariant Violation: assuming that false equals true is asking for trouble
fuzz/map-cases-path-condition-leak.jsErrorPassFatalError PP0001: ; FatalError PP0037: failed to recover from a for/while loop bail-out due to unsupported logic in loop body
fuzz/merged-join-uses-earlier-declared-value.jsErrorPassFatalError PP0001:
fuzz/nested-try-leaked-object-mutation.jsErrorPassRecoverableError PP0021: Possible throw inside try/catch is not yet supported (x2)
fuzz/not-nullish-falsy-logical.jsWrongPassexpected "nullish,nullish,0,,false,NaN,true" got "nullish,nullish,true,true,true,true,true"
fuzz/path-implies-both-true-visitor.jsErrorPassInvariant Violation:
fuzz/primitive-receiver-strict-set.jsWrongPassexpected "throw:TypeError,throw:TypeError,throw:TypeError,throw:RangeError,ok,ok,throw:RangeError|log=2" got "ok,ok,throw:TypeError,throw:RangeError,ok,throw:Ty
fuzz/pure-unary-coercion-throws.jsErrorPassRecoverableError PP0008: might be a symbol or an object with an unknown valueOf or toString or Symbol.toPrimitive method (x2)
fuzz/residual-loop-closure-mutated-param.jsWrongPassexpected "throw:TypeError,NaN" got "throw:TypeError,abc1"
fuzz/switch-empty-nan-discriminant.jsErrorPassInvariant Violation:
fuzz/switch-hoisted-var-loop.jsErrorPassInvariant Violation:
fuzz/switch-nested-abrupt-default.jsErrorPassInvariant Violation:
fuzz/throw-before-try-catch-finally.jsWrongPassexpected "throw:before,ok,throw:before,ok,finally,throw:before,swallowed" got "caught:before,ok,throw:before,ok,finally+finally,swallowed,swallowed"
fuzz/throw-in-call-argument-join.jsErrorPassFatalError PP0001:
fuzz/unknown-key-write-property-order.jsErrorPassFatalError PP0001: (x2)
fuzz/unknown-property-concrete-get-partial.jsErrorPassInvariant Violation:
fuzz/unknown-property-concrete-get.jsErrorPassInvariant Violation:
fuzz/unused-in-non-object-throws.jsWrongPassexpected "throw:TypeError,throw:TypeError,throw:TypeError,1,1" got "1,1,1,1,1"
fuzz/unused-property-read-throws.jsWrongPassexpected "throw:TypeError,throw:TypeError,1,1,1,1,throw:getter|getterRan=1" got "1,1,1,1,1,1,1|getterRan=0"
fuzz/update-expression-string.jsWrongPassexpected "1,1,NaN,1,NaN,2" got "1,01,a1,1,NaN,2"
fuzz/widen-nested-loop-in-helper.jsErrorPassInvariant Violation:
real-world/babel-class-hierarchy.jsPassPass
real-world/config-parser-concrete.jsPassPass
real-world/config-parser-env-override.jsErrorPassFatalError PP0031: property access on unknown object
real-world/event-emitter-init.jsPassPass
real-world/i18n-plural-rules.jsPassPass
real-world/lodash-init.jsPassPass
real-world/lookup-table-char-classes.jsErrorPassFatalError PP0001:
real-world/module-system.jsToo largePassoutput 2207B is 2.04x input 1080B (max 2x)
real-world/react-like-render.jsErrorPassFatalError PP0001: (x4)
real-world/template-compiler.jsPassPass
real-world/template-render-optimized.jsPassPass
real-world/url-router.jsErrorPassFatalError PP0001: (x5)
real-world-libs/crc32-sequential-loop-leaked-typedarray.jsPassPass
real-world-libs/d3-internmap-class-extends-map.jsErrorPassInvariant Violation: serialized 7 of 8
real-world-libs/d3-scale-default-param-factory.jsErrorPassFatalError: TODO: do not know how to deal with non-Identifier parameters
real-world-libs/handlebars-globalthis-definegetter.jsWrongPassexpected "4.7.10:a&amp;&lt;b>" got "TypeError: not an object"
real-world-libs/marked-class-static-getter.jsErrorPassFatalError PP0022: a class method incorrectly went through the serializeValue() code path
real-world-libs/marked-optional-catch-binding.jsPassPass
real-world-libs/redux-random-split-join.jsErrorPassFatalError PP0001: This operation is not yet supported on abstract value
real-world-libs/underscore-document-childnodes.jsErrorPassFatalError PP0001: This operation is not yet supported on document at childNodes