prepack/scripts/debug-fb-www.js

217 lines
7.0 KiB
JavaScript
Raw Permalink Normal View History

/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/* @flow */
// NOTE:
// put the input fb-www file in ${root}/fb-www/input.js
// the compiled file will be saved to ${root}/fb-www/output.js
let prepackSources = require("../lib/prepack-node.js").prepackSources;
let path = require("path");
let { readFile, writeFile, existsSync } = require("fs");
let { promisify } = require("util");
let readFileAsync = promisify(readFile);
let writeFileAsync = promisify(writeFile);
let chalk = require("chalk");
let { Linter } = require("eslint");
let lintConfig = require("./lint-config");
let errorsCaptured = [];
function printError(error) {
let msg = `${error.errorCode}: ${error.message}`;
if (error.location) {
let loc_start = error.location.start;
let loc_end = error.location.end;
msg += ` at ${loc_start.line}:${loc_start.column} to ${loc_end.line}:${loc_end.column}`;
}
switch (error.severity) {
case "Information":
console.log(`Info: ${msg}`);
return "Recover";
case "Warning":
console.warn(`Warn: ${msg}`);
return "Recover";
case "RecoverableError":
case "FatalError":
console.error(`Error: ${msg}`);
return "Fail";
default:
console.log(msg);
}
}
let prepackOptions = {
errorHandler: diag => {
if (diag.severity === "Information") {
console.log(diag.message);
return "Recover";
}
errorsCaptured.push(diag);
if (diag.severity !== "Warning") {
if (diag.errorCode === "PP0025") {
// recover from `unable to evaluate "key" and "ref" on a ReactElement
return "Recover";
}
if (diag.errorCode === "PP0021") {
// recover from "Possible throw inside try/catch is not yet supported"
return "Recover";
}
return "Fail";
}
return "Recover";
},
compatibility: "fb-www",
internalDebug: true,
serialize: true,
uniqueSuffix: "",
maxStackDepth: 200,
instantRender: false,
reactEnabled: true,
reactOutput: "jsx",
reactVerbose: true,
Rearchitect `evaluatePure` and side-effect tracking (#2587) Summary: Release notes: the side-effect detection system within pure scopes has been improved This PR revamps the entire `evaluatePure` system. The old system had so many small issues with it was the cause of many edge-cases that were hard to patch up. Specifically, the PR makes the following changes: - `realm.evaluatePure` -> `realm.evaluateWithPureScope` and now can only be used a single instance. Nesting further `realm.evaluateWithPureScope` calls will trigger an invariant and is strictly forbidden. Furthermore, it now only takes a single argument – the function you're wrapped around. - All places that used pure scope wrappers have been updated to conditionally enable it depending if the `realm.isInPureScope()` returns `true` or `false`. - `realm.evaluateFunctionForPureEffects` has been added, this works like `evaluateForEffects` except it requires a root function and a callback for side-effects. The callback for side-effects works like the old callback that was `evaluatePure`. - `realm.evaluateFunctionForPureEffectsInGlobalEnv` has been added a convenience wrapper around `realm.evaluateFunctionForPureEffects`. - When we leak bindings, we no longer set their value to `undefined` or `realm.intrinsics.undefined`. We now set the value to a special "leaked abstract", specifically – `realm.intrinsics.__leakedValue` – like `topValue` and `bottomValue`. Unsurprisingly, this now fixes a host of bugs that existed before. Including fixes for https://github.com/facebook/prepack/issues/2598, https://github.com/facebook/prepack/issues/2579, https://github.com/facebook/prepack/issues/2446, https://github.com/facebook/prepack/issues/2599 and probably many other issues too. The logic for detection of side-effects works very differently from before but after speaking to sebmarkbage last week, he pointed me in this direction to track side-effects rather than how we did it before. We now find side-effects from a given set of effects, rather than in an ad-hoc manor as mutations occur on objects/bindings. This PR requires https://github.com/facebook/prepack/pull/2596 as a dependency as it re-uses the logic. Closes https://github.com/facebook/prepack/pull/2587. Fixes https://github.com/facebook/prepack/issues/2598. Fixes https://github.com/facebook/prepack/issues/2579. Fixes https://github.com/facebook/prepack/issues/2446. Fixes https://github.com/facebook/prepack/issues/2599. Pull Request resolved: https://github.com/facebook/prepack/pull/2600 Differential Revision: D10368159 Pulled By: trueadm fbshipit-source-id: ded248f5cfd8648913cae9b9c697d723a82c59ab
2018-10-13 01:42:44 +03:00
reactFailOnUnsupportedSideEffects: true,
reactOptimizeNestedFunctions: true,
arrayNestedOptimizedFunctionsEnabled: true,
inlineExpressions: true,
Introducting new option --invariantLevel NUMBER and dropping --omitInvariants Summary: Release notes: Introducting new option --invariantLevel NUMBER and dropping --omitInvariants The new default is invariant level 0, which corresponds to the old --omitInvariants. Level 1 is roughly what we used to do: Check invariants on (derived) abstract values. Level 2 implements #1180: Checking that all accesses to built-ins do what Prepack expects them to do, including checks for when properties on built-ins are absent. Level 3 adds checks for internal consistency, basically an internal debug level. The serializer tests now run with the highest invariant level by default. The added invariants found a few issues that got addressed, including: - Prepack exposes a TypedArray prototype, which is not a real thing. It's now marked as `__refuseSerialization`, and no invariants are emitted for such things. - Global variables / properties on the global object are special. Those are not yet handled at level 2. - Accesses to Prepack magic functions that generally start with `__` should not produce invariant checks. - Magic code generation for loops should not take into account objects that are `__refuseSerialization`. - All invariant statements get a unique id to make it easier to locate them. - I marked some test cases which depend on counting occurrences in the output as "// omit invariants", as the additional invariants increased some such counts. As part of testing, I also found it necessary to make the --invariantMode more useful; it now also allows specifying nativeLoggingHook which is the preferred way of logging in React Native. To reduce the number of checks by a few orders of magnitude in practice, each property is only checked on first access. This is tracked by a global variable `__checkedBindings`. This pull requests incorporates all aspects of the #1709 (which I abandoned). Closes https://github.com/facebook/prepack/pull/1724 Reviewed By: simonhj Differential Revision: D7575091 Pulled By: NTillmann fbshipit-source-id: 585cd224ce66882f8e5f27d88c1ad08afeb96ee1
2018-04-12 21:24:04 +03:00
invariantLevel: 0,
Adds k limiting counter to simplification logic Summary: Release notes: adds an abstract value limiting counter Following some discussion I had the other night with hermanventer in regards to having a counter for Prepack abstract logic computation, so rather than hang the compilation process, we could throw and recover using the pure logic. This PR adds a "k-limiting" counter to `AbstractValue`'s as a method `_checkAbstractValueImpliesCounter `. When we try and resolve abstract conditions on a single AbstractValue and we reach the limit, a `CompilerDiagnostic` along with a `FatalError` gets thrown. Locally this works well for the React compiler input. I only call `_checkAbstractValueImpliesCounter ` from `implies` right now and that might not be the best place, maybe it should be checked in other places too, but `implies` seemed the good place as it was in all stackframes when debugging. This also adds a new option called `abstractValueImpliesCounter ` which is based as an option to Realm. By default this feature is not turned on when the option `abstractValueImpliesCounter ` value is `0` (which it has been set to). Maybe we should enable this by default and set it to a sensible level? **Note:** I tried using the `timeout` feature, but it didn't work and wasn't too reliable in general because it would always fire in places where I didn't care for "time taken" but rather the depth on a certain feature of computation – in this case, abstract logic simplification. Enabling the timeout causes many other parts of compilation to break unless I explicitly set the timeout to `0` through all the React reconciliation work, which kind of defeats the purpose of it (the React reconciler work can take minutes to deal with on very large, expensive trees, and this is totally expected). Furthermore, we should avoid using non-deterministic features like `feature` in such cases as they can skew test results. Closes https://github.com/facebook/prepack/pull/1607 Differential Revision: D7312616 Pulled By: trueadm fbshipit-source-id: 8763df09b3182af4a496dec42d507aff1bb2cc8d
2018-03-17 03:25:19 +03:00
abstractValueImpliesMax: 1000,
stripFlow: true,
};
let inputPath = path.resolve("fb-www/input.js");
let outputPath = path.resolve("fb-www/output.js");
let componentsListPath = path.resolve("fb-www/components.txt");
let components = new Map();
let startTime = Date.now();
let uniqueEvaluatedComponents = 0;
function compileSource(source) {
let serialized;
try {
serialized = prepackSources([{ filePath: inputPath, fileContents: source, sourceMapContents: "" }], prepackOptions);
} catch (e) {
console.log(`\n${chalk.inverse(`=== Diagnostics Log ===`)}\n`);
errorsCaptured.forEach(error => printError(error));
throw e;
}
return {
stats: serialized.reactStatistics,
code: serialized.code,
};
}
async function readComponentsList() {
if (existsSync(componentsListPath)) {
let componentsList = await readFileAsync(componentsListPath, "utf8");
let componentNames = componentsList.split("\n");
for (let componentName of componentNames) {
components.set(componentName, "missing");
}
}
}
function lintCompiledSource(source) {
let linter = new Linter();
let errors = linter.verify(source, lintConfig);
if (errors.length > 0) {
console.log(`\n${chalk.inverse(`=== Validation Failed ===`)}\n`);
for (let error of errors) {
console.log(`${chalk.red(error.message)} ${chalk.gray(`(${error.line}:${error.column})`)}`);
}
process.exit(1);
}
}
async function compileFile() {
let source = await readFileAsync(inputPath, "utf8");
let { stats, code } = await compileSource(source);
await writeFileAsync(outputPath, code);
lintCompiledSource(code);
// $FlowFixMe: no idea what this is about
return stats;
}
function printReactEvaluationGraph(evaluatedRootNode, depth) {
if (Array.isArray(evaluatedRootNode)) {
for (let child of evaluatedRootNode) {
printReactEvaluationGraph(child, depth);
}
} else {
let status = evaluatedRootNode.status.toLowerCase();
let message = evaluatedRootNode.message !== "" ? `: ${evaluatedRootNode.message}` : "";
let name = evaluatedRootNode.name;
let line;
if (status === "inlined") {
line = `${chalk.gray(`-`)} ${chalk.green(name)} ${chalk.gray(`(${status + message})`)}`;
Throw an error when side-effectful logic happens in a React component tree Summary: Release notes: the React reconciler now throw a FatalError upon encountering side-effects in a render This PR revamps the current React system's restrictions for what you can and can't do during the React reconcilation phase. This is a pretty large update but provides much better boundaries between what is "safe" and not "safe", thus reducing the constraints. 1. A new error `ReconcilerRenderBailOut` is thrown if something occurs in the React reconciliation that causes the render to fail and it's a hard fail – no recovering and continuing. 2. If you mutate a binding/object outside the render phase, given the React component render phase is meant to be "pure", a `ReconcilerRenderBailOut` will be thrown. 3. If you `throw` during the React reconciliation phase, again a `ReconcilerRenderBailOut` will be thrown. In the future, we should maybe loosen the constraints around all this and maybe allow `throw`, but right now it's causing too much friction. We should attempt to make React components render phase as pure as possible – as it results in much better optimizations by a compiler because we can assert far more without things tripping us up. Another point, regarding (1), is that we should ideally be able to recover from the error thrown in Prepack. The reason we can't and is something that might be a very deep issue in Prepack, is that effects don't properly restore when we have nested PossiblyNormalCompletions at work. Bindings get mutated on records from changes made within `evaluateForEffects` but never get reset when in nested PossiblyNormalCompletion. If I remove all the things that can cause `PossiblyNormalCompletion`s then everything works fine and bindings do get restored. We can remove the constraint on (1) once we've found and fixed that issue. Closes https://github.com/facebook/prepack/pull/1860 Differential Revision: D7950562 Pulled By: trueadm fbshipit-source-id: 4657e68b084c7069622e88c9655823b5f1f9386f
2018-05-10 17:27:27 +03:00
} else if (status === "unknown_type" || status === "bail-out" || status === "fatal") {
line = `${chalk.gray(`-`)} ${chalk.red(name)} ${chalk.gray(`(${status + message})`)}`;
} else {
line = `${chalk.gray(`-`)} ${chalk.yellow(name)} ${chalk.gray(`(${status + message})`)}`;
}
if (components.has(name)) {
let currentStatus = components.get(name);
if (currentStatus === "missing") {
uniqueEvaluatedComponents++;
currentStatus = [currentStatus];
components.set(name, currentStatus);
} else if (Array.isArray(currentStatus)) {
currentStatus.push(status);
}
}
console.log(line.padStart(line.length + depth));
printReactEvaluationGraph(evaluatedRootNode.children, depth + 2);
}
}
readComponentsList()
.then(compileFile)
.then(result => {
console.log(`\n${chalk.inverse(`=== Compilation Complete ===`)}\n`);
console.log(chalk.bold(`Evaluated Tree:`));
printReactEvaluationGraph(result.evaluatedRootNodes, 0);
if (components.size > 0) {
console.log(`\n${chalk.inverse(`=== Status ===`)}\n`);
for (let [componentName, status] of components) {
if (status === "missing") {
console.log(`${chalk.red(``)} ${componentName}`);
} else {
console.log(`${chalk.green(``)} ${componentName}`);
}
}
}
console.log(`\n${chalk.inverse(`=== Summary ===`)}\n`);
if (components.size > 0) {
console.log(`${chalk.gray(`Optimized Components`)}: ${uniqueEvaluatedComponents}/${components.size}`);
}
console.log(`${chalk.gray(`Optimized Nodes`)}: ${result.componentsEvaluated}`);
console.log(`${chalk.gray(`Inlined Nodes`)}: ${result.inlinedComponents}`);
console.log(`${chalk.gray(`Optimized Trees`)}: ${result.optimizedTrees}`);
console.log(`${chalk.gray(`Optimized Nested Closures`)}: ${result.optimizedNestedClosures}`);
let timeTaken = Math.floor((Date.now() - startTime) / 1000);
console.log(`${chalk.gray(`Compile time`)}: ${timeTaken}s\n`);
// warning about ref and keys
console.log(`Warning: the build assumes that ref and key aren't being spread.`);
})
.catch(e => {
console.log(`\n${chalk.inverse(`=== Compilation Failed ===`)}\n`);
Throw an error when side-effectful logic happens in a React component tree Summary: Release notes: the React reconciler now throw a FatalError upon encountering side-effects in a render This PR revamps the current React system's restrictions for what you can and can't do during the React reconcilation phase. This is a pretty large update but provides much better boundaries between what is "safe" and not "safe", thus reducing the constraints. 1. A new error `ReconcilerRenderBailOut` is thrown if something occurs in the React reconciliation that causes the render to fail and it's a hard fail – no recovering and continuing. 2. If you mutate a binding/object outside the render phase, given the React component render phase is meant to be "pure", a `ReconcilerRenderBailOut` will be thrown. 3. If you `throw` during the React reconciliation phase, again a `ReconcilerRenderBailOut` will be thrown. In the future, we should maybe loosen the constraints around all this and maybe allow `throw`, but right now it's causing too much friction. We should attempt to make React components render phase as pure as possible – as it results in much better optimizations by a compiler because we can assert far more without things tripping us up. Another point, regarding (1), is that we should ideally be able to recover from the error thrown in Prepack. The reason we can't and is something that might be a very deep issue in Prepack, is that effects don't properly restore when we have nested PossiblyNormalCompletions at work. Bindings get mutated on records from changes made within `evaluateForEffects` but never get reset when in nested PossiblyNormalCompletion. If I remove all the things that can cause `PossiblyNormalCompletion`s then everything works fine and bindings do get restored. We can remove the constraint on (1) once we've found and fixed that issue. Closes https://github.com/facebook/prepack/pull/1860 Differential Revision: D7950562 Pulled By: trueadm fbshipit-source-id: 4657e68b084c7069622e88c9655823b5f1f9386f
2018-05-10 17:27:27 +03:00
if (e.__isReconcilerFatalError) {
console.error(e.message + "\n");
printReactEvaluationGraph(e.evaluatedNode, 0);
} else {
console.error(e.nativeStack || e.stack);
}
process.exit(1);
});