Preserve residual funciton original ordering

Summary:
Release Note: preserve residual functions' original source ordering.

We serialize the residual functions in random order(based on the visitor's discovery of residual function order) which may result in very bad code locality in real world app.
Add the feature to preserve residual functions' original source ordering.

Note:
1. I did not make an option for this feature because I think it should do this by default.
2. To gain better perf, we may introduce some profiler guided optimization in future.
Closes https://github.com/facebook/prepack/pull/1163

Differential Revision: D6437299

Pulled By: yinghuitan

fbshipit-source-id: 9c0a93a10862b0d8c96bfccb38238c497e4012e7
This commit is contained in:
Jeffrey Tan 2017-11-29 10:58:48 -08:00 committed by Facebook Github Bot
parent 10215dd8cc
commit e03c61c29f
5 changed files with 69 additions and 0 deletions

View File

@ -9,6 +9,7 @@
/* @flow */
import invariant from "../lib/invariant.js";
let FatalError = require("../lib/errors.js").FatalError;
let prepackSources = require("../lib/prepack-node.js").prepackSources;
@ -231,6 +232,28 @@ function execInContext(code) {
return (result + logOutput).trim();
}
function parseFunctionOrderings(code: string): Array<number> {
const orders = [];
const functionOrderPattern = /Function ordering: (\d+)/g;
let match;
while ((match = functionOrderPattern.exec(code)) != null) {
orders.push(match[1]);
}
return orders;
}
function verifyFunctionOrderings(code: string): boolean {
const orders = parseFunctionOrderings(code);
for (let i = 1; i < orders.length; ++i) {
invariant(orders[i] !== orders[i - 1]);
if (orders[i] < orders[i - 1]) {
console.log(chalk.red(`Funtion ordering is not preserved: function ${orders[i - 1]} is before ${orders[i]}`));
return false;
}
}
return true;
}
function runTest(name, code, options, args) {
console.log(chalk.inverse(name) + " " + JSON.stringify(options));
let compatibility = code.includes("// jsc") ? "jsc-600-1-4-17" : undefined;
@ -420,6 +443,9 @@ function runTest(name, code, options, args) {
console.log(chalk.red("Output mismatch!"));
break;
}
if (!verifyFunctionOrderings(codeToRun)) {
break;
}
// Test the number of clone functions generated with the inital prepack call
if (i === 0 && functionCloneCountMatch) {
let functionCount = parseInt(functionCloneCountMatch[1], 10);

View File

@ -27,6 +27,8 @@ export default function(
let ConciseBody = ast.body;
if (ConciseBody.type !== "BlockStatement") {
ConciseBody = t.blockStatement([t.returnStatement(ConciseBody)]);
// Use original array function's location for the new concise body.
ConciseBody.loc = ast.body.loc;
}
// 1. If the function code for this ArrowFunction is strict mode code, let strict be true. Otherwise let strict be false.
@ -40,6 +42,7 @@ export default function(
// 4. Let closure be FunctionCreate(Arrow, parameters, ConciseBody, scope, strict).
let closure = Functions.FunctionCreate(realm, "arrow", parameters, ConciseBody, scope, strict);
closure.loc = ast.loc;
// 5. Return closure.
return closure;

View File

@ -142,6 +142,7 @@ export default function(
// 3. Let closure be FunctionCreate(Normal, FormalParameters, FunctionBody, scope, strict).
let closure = Functions.FunctionCreate(realm, "normal", ast.params, ast.body, scope, strict);
closure.loc = ast.loc;
// 4. Perform MakeConstructor(closure).
MakeConstructor(realm, closure);

View File

@ -169,6 +169,30 @@ export class ResidualFunctions {
return factoryFunctionInfos;
}
// Preserve residual functions' ordering from original source code.
// This is necessary to prevent unexpected code locality issues.
// [Algorithm] sort function based on following criterias:
// 1. source file alphabetically.
// 2. start line number.
// 3. start column number.
_sortFunctionByOriginalOrdering(functionEntries: Array<[BabelNodeBlockStatement, Array<FunctionInstance>]>): void {
functionEntries.sort((funcA, funcB) => {
const funcALocation = funcA[0].loc;
const funcBLocation = funcB[0].loc;
if (!funcALocation || !funcBLocation || !funcALocation.source || !funcBLocation.source) {
// Preserve the current ordering if there is no source location information available.
return -1;
}
if (funcALocation.source !== funcBLocation.source) {
return funcALocation.source.localeCompare(funcBLocation.source);
} else if (funcALocation.start.line !== funcBLocation.start.line) {
return funcALocation.start.line - funcBLocation.start.line;
} else {
return funcALocation.start.column - funcBLocation.start.column;
}
});
}
spliceFunctions(
rewrittenAdditionalFunctions: Map<FunctionValue, Array<BabelNodeStatement>>
): ResidualFunctionsResult {
@ -200,6 +224,7 @@ export class ResidualFunctions {
let functionEntries: Array<[BabelNodeBlockStatement, Array<FunctionInstance>]> = Array.from(
this.functions.entries()
);
this._sortFunctionByOriginalOrdering(functionEntries);
this.statistics.functions = functionEntries.length;
let unstrictFunctionBodies = [];
let strictFunctionBodies = [];

View File

@ -0,0 +1,14 @@
(function () {
first = function() {
// Function ordering: 1
second();
return 10;
}
var second = function() {
// Function ordering: 2
return 20;
};
inspect = function() {
return first() + second();
}
})();