Skip to content

Commit

Permalink
Reduce property lookups [perf]
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Sep 3, 2023
1 parent c15f363 commit 723df96
Showing 1 changed file with 81 additions and 71 deletions.
152 changes: 81 additions & 71 deletions lib/serialize/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,29 @@ module.exports = {
processBlock(block, getParentCreateScopeRecord, inheritedVars, frozenNames, isRoot) {
const returnNodes = [];

// Init local vars object to contain all var nodes for local vars
// so they can have names changed if necessary + counters for defined params
const paramNames = [...block.paramNames],
localVars = Object.create(null), // Keyed by param name
paramDefinedCounts = Object.create(null); // Keyed by param name
for (const paramName of paramNames) {
localVars[paramName] = [];
paramDefinedCounts[paramName] = 0;
}
// Init param objects
const paramsByName = Object.create(null);
const params = [...block.paramNames].map((name) => {
const param = {
name,
// Identifier nodes referring to this param
localVarNodes: [],
// If param always contains another function defined in this block,
// function definition and array of function records
fnDef: null,
fnRecords: null,
// Set to `true` if param does not always contain another function defined in this block
isNotLocalFunction: false,
// Count of scopes which define a value for this param
definedCount: 0,
// Index of function to inject value for param in returned functions
injectionIndex: null,
// Node for parameter to injection function
injectionVarNode: null
};
paramsByName[name] = param;
return param;
});

// Identify scope vars which refer to functions within this scope.
// If value of a scope var is consistently a function created within this block
Expand All @@ -85,20 +99,20 @@ module.exports = {
}
}

const paramsFunctions = Object.create(null), // Keyed by param name
undefinedRecord = this.serializeValue(undefined);
const undefinedRecord = this.serializeValue(undefined);
let numLocalFunctions = 0;
for (const scope of blockScopes.values()) {
const {values} = scope;
for (const paramName of paramNames) {
const valProps = values[paramName];
for (const param of params) {
const valProps = values[param.name];
if (!valProps) continue;

// Flag references to functions within this block as circular and increment count where not
const valRecord = valProps.record;
let localFunction;
if (valRecord === undefinedRecord) {
// Value is undefined - treat same as unused
values[paramName] = undefined;
values[param.name] = undefined;
} else {
localFunction = localFunctions.get(valRecord);
if (localFunction) {
Expand Down Expand Up @@ -131,41 +145,41 @@ module.exports = {
if (isCircular) {
valProps.isCircular = true;
} else {
paramDefinedCounts[paramName]++;
param.definedCount++;
}
}
}

// Determine if param value refers to a function in this scope
const paramsFunction = paramsFunctions[paramName];
if (paramsFunction === false) continue; // Already discounted

if (localFunction && localFunction.scope === scope) {
// Value refers to a function created in this scope
if (!paramsFunction) {
paramsFunctions[paramName] = {fnDef: localFunction.fnDef, fnRecords: [valRecord]};
} else if (paramsFunction.fnDef === localFunction.fnDef) {
paramsFunction.fnRecords.push(valRecord);
if (param.isNotLocalFunction) continue; // Already discounted

const isLocalFunction = !!localFunction && localFunction.scope === scope;
if (!param.fnDef) {
if (isLocalFunction) {
param.fnDef = localFunction.fnDef;
param.fnRecords = [valRecord];
numLocalFunctions++;
} else {
// Value refers to different function from this scope
paramsFunctions[paramName] = false;
param.isNotLocalFunction = true;
}
} else if (isLocalFunction && param.fnDef === localFunction.fnDef) {
param.fnRecords.push(valRecord);
} else {
// Value refers to something else
paramsFunctions[paramName] = false;
param.isNotLocalFunction = true;
param.fnDef = null;
param.fnRecords = null;
numLocalFunctions--;
}
}
}

let numInternalOnlyFunctions = 0;
const paramsFunctionsArr = Object.entries(paramsFunctions);
if (paramsFunctionsArr.length > 0) {
if (numLocalFunctions > 0) {
const internalFunctions = new Set();
for (const [paramName, paramsFunction] of paramsFunctionsArr) {
if (!paramsFunction) continue;
for (const {name: paramName, fnDef, fnRecords} of params) {
if (!fnDef) continue;

// Record param name containing this function in function def object
const {fnDef, fnRecords} = paramsFunction;
(fnDef.internalScopeParamNames || (fnDef.internalScopeParamNames = [])).push(paramName);

// Delete values for this param from all scopes.
Expand Down Expand Up @@ -198,31 +212,24 @@ module.exports = {
}

// Re-order params with least often defined last
paramNames.sort((paramName1, paramName2) => {
const count1 = paramDefinedCounts[paramName1],
count2 = paramDefinedCounts[paramName2];
return count1 > count2 ? -1 : count2 > count1 ? 1 : 0;
});
params.sort(({definedCount: count1}, {definedCount: count2}) => (
count1 > count2 ? -1 : count2 > count1 ? 1 : 0
));

// Function to create functions to inject values into scope
const injectionIndexes = Object.create(null),
injectionVarNodes = Object.create(null);
function createInjection(paramName) {
let index = injectionIndexes[paramName];
if (index !== undefined) return index;

const inputParamNode = t.identifier(`_${paramName}`),
outputParamNode = t.identifier(paramName);
injectionVarNodes[paramName] = inputParamNode;
localVars[paramName].push(outputParamNode);
function createInjection(param) {
const inputParamNode = t.identifier(`_${param.name}`),
outputParamNode = t.identifier(param.name);
param.injectionVarNode = inputParamNode;
param.localVarNodes.push(outputParamNode);

const injectNode = t.arrowFunctionExpression(
[inputParamNode],
t.assignmentExpression('=', outputParamNode, inputParamNode)
);

index = returnNodes.length;
injectionIndexes[paramName] = index;
const index = returnNodes.length;
param.injectionIndex = index;
returnNodes.push(injectNode);
return index;
}
Expand All @@ -242,8 +249,8 @@ module.exports = {
undefinedIndexes = [];
let numTrailingUndefined = 0,
paramIndex = 0;
for (const paramName of paramNames) {
const valProps = values[paramName];
for (const param of params) {
const valProps = values[param.name];
let node;
if (!valProps) {
// Value not required - insert `undefined` as empty value
Expand All @@ -259,13 +266,13 @@ module.exports = {
node = valRecord.varNode;
if (valProps.isCircular) {
// Circular reference - inject into scope later
const injectionIndex = createInjection(paramName);
const injectionIndex = param.injectionIndex ?? createInjection(param);

// Create var for inject function for this scope.
// Each var will only be injected once into each scope, so no need to guard against
// duplicate inject function vars being created.
const injectRecord = createRecord(
`inject${upperFirst(paramName)}Into${upperFirst(scopeRecord.varNode.name)}`
`inject${upperFirst(param.name)}Into${upperFirst(scopeRecord.varNode.name)}`
);

const injectFnNode = t.memberExpression(
Expand Down Expand Up @@ -334,7 +341,7 @@ module.exports = {
&& returnNodeIndex + blockFunctions.length - numInternalOnlyFunctions + childBlocks.length === 1;

// If block contains `eval()`, freeze all param names
if (containsEval) frozenNames = new Set([...frozenNames, ...paramNames]);
if (containsEval) frozenNames = new Set([...frozenNames, ...params.map(param => param.name)]);

// Init vars to track strict/sloppy children
const strictFns = [];
Expand Down Expand Up @@ -463,10 +470,10 @@ module.exports = {
// Add var nodes to globals/locals
const fnReservedVarNames = new Set();
for (const varName in externalVars) {
const localVarNodes = localVars[varName];
if (localVarNodes) {
const param = paramsByName[varName];
if (param) {
// Local var
localVarNodes.push(...externalVars[varName]);
param.localVarNodes.push(...externalVars[varName]);
} else {
// Var referencing upper scope
inheritedVars[varName].push(...externalVars[varName]);
Expand Down Expand Up @@ -524,7 +531,7 @@ module.exports = {

for (const paramName of internalScopeParamNames) {
const varNode = t.identifier(paramName);
localVars[paramName].push(varNode);
paramsByName[paramName].localVarNodes.push(varNode);
node = t.assignmentExpression('=', varNode, node);
}
}
Expand All @@ -537,7 +544,10 @@ module.exports = {
}
}

const nextInheritedVars = Object.assign(Object.create(null), inheritedVars, localVars);
const nextInheritedVars = Object.assign(Object.create(null), inheritedVars);
for (const param of params) {
nextInheritedVars[param.name] = param.localVarNodes;
}

// Process child blocks
for (const childBlock of childBlocks) {
Expand Down Expand Up @@ -607,22 +617,22 @@ module.exports = {
{mangle} = this.options;
let hasArgumentsOrEvalParam = false,
frozenThisVarName, frozenArgumentsVarName;
for (const paramName of paramNames) {
for (const param of params) {
const paramName = param.name;
let newName;
const injectionVarNode = injectionVarNodes[paramName],
renameInjectionVarNode = () => {
if (!injectionVarNode) return;
injectionVarNode.name = mangle
? (newName === 'a' ? 'b' : 'a')
: `_${newName}`;
};
const renameInjectionVarNode = () => {
if (!param.injectionVarNode) return;
param.injectionVarNode.name = mangle
? (newName === 'a' ? 'b' : 'a')
: `_${newName}`;
};

if (paramName === 'new.target') {
// `new.target` is always renamed as it can't be provided for `eval()` anyway
newName = transformVarName('newTarget', containsEval);

// Convert `MetaProperty` nodes into `Identifier`s with new name
for (const varNode of localVars[paramName]) {
for (const varNode of param.localVarNodes) {
varNode.type = 'Identifier';
varNode.name = newName;
varNode.meta = undefined;
Expand All @@ -635,7 +645,7 @@ module.exports = {
newName = transformVarName(paramName);
if (newName !== paramName) {
// Rename all nodes
for (const varNode of localVars[paramName]) {
for (const varNode of param.localVarNodes) {
varNode.name = newName;
}

Expand All @@ -656,7 +666,7 @@ module.exports = {
}

assert(
!injectionVarNode,
!param.injectionVarNode,
'Cannot handle redefined `arguments` in function containing `eval()`'
);
} else {
Expand Down

0 comments on commit 723df96

Please sign in to comment.