2018-02-23 04:46:50 +03:00
|
|
|
/**
|
|
|
|
* 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");
|
2018-03-15 02:18:19 +03:00
|
|
|
let { readFile, writeFile, existsSync } = require("fs");
|
2018-02-23 04:46:50 +03:00
|
|
|
let { promisify } = require("util");
|
|
|
|
let readFileAsync = promisify(readFile);
|
|
|
|
let writeFileAsync = promisify(writeFile);
|
2018-03-15 02:18:19 +03:00
|
|
|
let chalk = require("chalk");
|
2018-03-15 16:37:05 +03:00
|
|
|
let { Linter } = require("eslint");
|
2018-05-23 01:29:28 +03:00
|
|
|
let lintConfig = require("./lint-config");
|
2018-02-23 04:46:50 +03:00
|
|
|
|
2018-03-04 13:07:37 +03:00
|
|
|
let errorsCaptured = [];
|
|
|
|
|
2018-03-21 16:40:52 +03:00
|
|
|
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":
|
2018-06-13 11:00:44 +03:00
|
|
|
console.error(`Error: ${msg}`);
|
2018-03-21 16:40:52 +03:00
|
|
|
return "Fail";
|
|
|
|
default:
|
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 04:46:50 +03:00
|
|
|
let prepackOptions = {
|
2018-03-04 13:07:37 +03:00
|
|
|
errorHandler: diag => {
|
2018-03-15 02:18:19 +03:00
|
|
|
if (diag.severity === "Information") {
|
|
|
|
console.log(diag.message);
|
|
|
|
return "Recover";
|
|
|
|
}
|
2018-03-21 16:40:52 +03:00
|
|
|
errorsCaptured.push(diag);
|
2018-03-15 02:18:19 +03:00
|
|
|
if (diag.severity !== "Warning") {
|
2018-03-21 21:23:09 +03:00
|
|
|
if (diag.errorCode === "PP0025") {
|
2018-05-26 04:15:52 +03:00
|
|
|
// recover from `unable to evaluate "key" and "ref" on a ReactElement
|
2018-03-21 21:23:09 +03:00
|
|
|
return "Recover";
|
|
|
|
}
|
2018-09-05 19:14:20 +03:00
|
|
|
if (diag.errorCode === "PP0021") {
|
|
|
|
// recover from "Possible throw inside try/catch is not yet supported"
|
|
|
|
return "Recover";
|
|
|
|
}
|
2018-03-07 01:09:03 +03:00
|
|
|
return "Fail";
|
|
|
|
}
|
|
|
|
return "Recover";
|
2018-03-04 13:07:37 +03:00
|
|
|
},
|
2018-02-23 04:46:50 +03:00
|
|
|
compatibility: "fb-www",
|
|
|
|
internalDebug: true,
|
|
|
|
serialize: true,
|
|
|
|
uniqueSuffix: "",
|
2018-09-05 19:14:20 +03:00
|
|
|
maxStackDepth: 200,
|
2018-06-29 06:51:05 +03:00
|
|
|
instantRender: false,
|
2018-02-23 04:46:50 +03:00
|
|
|
reactEnabled: true,
|
|
|
|
reactOutput: "jsx",
|
2018-03-15 02:18:19 +03:00
|
|
|
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,
|
2018-08-23 16:55:40 +03:00
|
|
|
reactOptimizeNestedFunctions: true,
|
|
|
|
arrayNestedOptimizedFunctionsEnabled: true,
|
2018-02-23 04:46:50 +03:00
|
|
|
inlineExpressions: true,
|
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,
|
2018-07-14 19:50:06 +03:00
|
|
|
stripFlow: true,
|
2018-02-23 04:46:50 +03:00
|
|
|
};
|
|
|
|
let inputPath = path.resolve("fb-www/input.js");
|
|
|
|
let outputPath = path.resolve("fb-www/output.js");
|
2018-03-15 02:18:19 +03:00
|
|
|
let componentsListPath = path.resolve("fb-www/components.txt");
|
|
|
|
let components = new Map();
|
|
|
|
let startTime = Date.now();
|
|
|
|
let uniqueEvaluatedComponents = 0;
|
2018-02-23 04:46:50 +03:00
|
|
|
|
|
|
|
function compileSource(source) {
|
2018-03-04 13:07:37 +03:00
|
|
|
let serialized;
|
|
|
|
try {
|
2018-03-16 21:41:50 +03:00
|
|
|
serialized = prepackSources([{ filePath: inputPath, fileContents: source, sourceMapContents: "" }], prepackOptions);
|
2018-03-04 13:07:37 +03:00
|
|
|
} catch (e) {
|
2018-03-21 16:40:52 +03:00
|
|
|
console.log(`\n${chalk.inverse(`=== Diagnostics Log ===`)}\n`);
|
|
|
|
errorsCaptured.forEach(error => printError(error));
|
2018-03-04 13:07:37 +03:00
|
|
|
throw e;
|
|
|
|
}
|
2018-02-23 04:46:50 +03:00
|
|
|
return {
|
|
|
|
stats: serialized.reactStatistics,
|
|
|
|
code: serialized.code,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-03-15 02:18:19 +03:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:37:05 +03:00
|
|
|
function lintCompiledSource(source) {
|
|
|
|
let linter = new Linter();
|
2018-05-23 01:29:28 +03:00
|
|
|
let errors = linter.verify(source, lintConfig);
|
2018-03-15 16:37:05 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 04:46:50 +03:00
|
|
|
async function compileFile() {
|
|
|
|
let source = await readFileAsync(inputPath, "utf8");
|
|
|
|
let { stats, code } = await compileSource(source);
|
|
|
|
await writeFileAsync(outputPath, code);
|
2018-03-15 16:37:05 +03:00
|
|
|
lintCompiledSource(code);
|
2018-03-19 21:22:15 +03:00
|
|
|
// $FlowFixMe: no idea what this is about
|
2018-02-23 04:46:50 +03:00
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
2018-03-01 02:03:14 +03:00
|
|
|
function printReactEvaluationGraph(evaluatedRootNode, depth) {
|
|
|
|
if (Array.isArray(evaluatedRootNode)) {
|
|
|
|
for (let child of evaluatedRootNode) {
|
|
|
|
printReactEvaluationGraph(child, depth);
|
|
|
|
}
|
|
|
|
} else {
|
2018-03-08 03:55:51 +03:00
|
|
|
let status = evaluatedRootNode.status.toLowerCase();
|
|
|
|
let message = evaluatedRootNode.message !== "" ? `: ${evaluatedRootNode.message}` : "";
|
2018-03-15 02:18:19 +03:00
|
|
|
let name = evaluatedRootNode.name;
|
|
|
|
let line;
|
|
|
|
if (status === "inlined") {
|
|
|
|
line = `${chalk.gray(`-`)} ${chalk.green(name)} ${chalk.gray(`(${status + message})`)}`;
|
2018-05-10 17:27:27 +03:00
|
|
|
} else if (status === "unknown_type" || status === "bail-out" || status === "fatal") {
|
2018-03-15 02:18:19 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2018-03-01 02:03:14 +03:00
|
|
|
console.log(line.padStart(line.length + depth));
|
|
|
|
printReactEvaluationGraph(evaluatedRootNode.children, depth + 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-15 02:18:19 +03:00
|
|
|
readComponentsList()
|
|
|
|
.then(compileFile)
|
2018-02-23 04:46:50 +03:00
|
|
|
.then(result => {
|
2018-03-15 02:18:19 +03:00
|
|
|
console.log(`\n${chalk.inverse(`=== Compilation Complete ===`)}\n`);
|
|
|
|
console.log(chalk.bold(`Evaluated Tree:`));
|
2018-03-01 02:03:14 +03:00
|
|
|
printReactEvaluationGraph(result.evaluatedRootNodes, 0);
|
2018-03-15 02:18:19 +03:00
|
|
|
|
|
|
|
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}`);
|
2018-03-22 20:12:54 +03:00
|
|
|
console.log(`${chalk.gray(`Optimized Nested Closures`)}: ${result.optimizedNestedClosures}`);
|
2018-03-15 02:18:19 +03:00
|
|
|
|
|
|
|
let timeTaken = Math.floor((Date.now() - startTime) / 1000);
|
|
|
|
console.log(`${chalk.gray(`Compile time`)}: ${timeTaken}s\n`);
|
2018-03-21 21:23:09 +03:00
|
|
|
// warning about ref and keys
|
|
|
|
console.log(`Warning: the build assumes that ref and key aren't being spread.`);
|
2018-02-23 04:46:50 +03:00
|
|
|
})
|
|
|
|
.catch(e => {
|
2018-03-21 16:40:52 +03:00
|
|
|
console.log(`\n${chalk.inverse(`=== Compilation Failed ===`)}\n`);
|
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);
|
|
|
|
}
|
2018-02-23 04:46:50 +03:00
|
|
|
process.exit(1);
|
|
|
|
});
|