Skip to content

Commit

Permalink
Instrument: Create function info functions at end of 2nd pass [refactor]
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Sep 3, 2023
1 parent 96bf1f3 commit 5faf3d4
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 44 deletions.
11 changes: 7 additions & 4 deletions lib/instrument/modify.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const {join: pathJoin, parse: pathParse} = require('path'),

// Imports
const Program = require('./visitors/program.js'),
{escapeFilename, hoistSloppyFunctionDeclarations} = require('./visitors/function.js'),
{
escapeFilename, hoistSloppyFunctionDeclarations, createFunctionInfoFunction
} = require('./visitors/function.js'),
{
createAndEnterBlock, createBindingWithoutNameCheck, createThisBinding, createArgumentsBinding,
createNewTargetBinding
Expand Down Expand Up @@ -84,7 +86,7 @@ function modifyAst(ast, filename, isCommonJs, isStrict, sources, evalState) {
internalVarsPrefixNum: 0,
trackerVarNode: undefined,
getScopeIdVarNode: undefined,
functionInfoNodes: [],
functions: [],
fileContainsFunctionsOrEval: false,
secondPass: (fn, ...params) => secondPassQueue.push({fn, params})
};
Expand Down Expand Up @@ -266,8 +268,9 @@ function insertImportStatement(programNode, state) {
* @returns {undefined}
*/
function insertFunctionInfoFunctions(programNode, isEvalCode, sources, state) {
const {functionInfoNodes} = state;
if (functionInfoNodes.length === 0) return;
if (state.functions.length === 0) return;

const functionInfoNodes = state.functions.map(fn => createFunctionInfoFunction(fn, state));

// Insert function to get sources
functionInfoNodes.push(
Expand Down
9 changes: 4 additions & 5 deletions lib/instrument/visitors/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ function visitClass(classNode, parent, key, className, state) {
}

// Serialize class AST
const astJson = serializeClassAst(classNode, constructorNode, constructorIndex);
fn.astJson = serializeClassAst(classNode, constructorNode, constructorIndex);

// If constructor or a prototype property contains `eval()`, make class name internal to class.
// Achieved by inserting an extra block between `super` block and constructor params
Expand Down Expand Up @@ -288,7 +288,7 @@ function visitClass(classNode, parent, key, className, state) {
state.secondPass(
instrumentClass,
classNode, fn, parent, key, constructorNode, constructorParamsBlock, constructorBodyBlock,
superBlock, astJson, state
superBlock, state
);
}

Expand Down Expand Up @@ -424,13 +424,12 @@ function serializeClassAst(classNode, constructorNode, constructorIndex) {
* @param {Object} constructorParamsBlock - Constructor params block object
* @param {Object} constructorBodyBlock - Constructor body block object
* @param {Object} superBlock - `super` target block object
* @param {string} astJson - JSON-stringified class AST
* @param {Object} state - State object
* @returns {undefined}
*/
function instrumentClass(
classNode, fn, parent, key, constructorNode, constructorParamsBlock, constructorBodyBlock,
superBlock, astJson, state
superBlock, state
) {
// If class has no constructor, create one for tracker code to be inserted in
if (!constructorNode) {
Expand All @@ -454,7 +453,7 @@ function instrumentClass(

// Add tracker code + block vars to constructor
instrumentFunctionOrClassConstructor(
constructorNode, fn, constructorParamsBlock, constructorBodyBlock, astJson, state
constructorNode, fn, constructorParamsBlock, constructorBodyBlock, state
);

// Insert tracking comment
Expand Down
61 changes: 26 additions & 35 deletions lib/instrument/visitors/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module.exports = {
withStrictModeState,
hoistSloppyFunctionDeclarations,
instrumentFunctionOrClassConstructor,
insertTrackerComment
insertTrackerComment,
createFunctionInfoFunction
};

// Modules
Expand Down Expand Up @@ -228,7 +229,7 @@ function visitFunctionOrMethod(
}

// Serialize AST
const astJson = serializeFunctionAst(fnNode, hasBodyBlock, isStrict, isEnteringStrict);
fn.astJson = serializeFunctionAst(fnNode, hasBodyBlock, isStrict, isEnteringStrict);

// Exit function
state.currentBlock = paramsBlock.parent;
Expand All @@ -239,9 +240,7 @@ function visitFunctionOrMethod(
parent[key] = null;

// Queue instrumentation of function in 2nd pass
state.secondPass(
instrumentFunction, fnNode, fn, parent, key, astJson, paramsBlock, bodyBlock, state
);
state.secondPass(instrumentFunction, fnNode, fn, parent, key, paramsBlock, bodyBlock, state);

return fn;
}
Expand Down Expand Up @@ -427,12 +426,14 @@ function createFunction(id, node, isStrict, state) {
const fn = {
id,
node,
astJson: undefined,
isStrict,
parent: parentFunction,
children: [],
trail: parentFunction ? [...state.trail] : undefined,
internalVars: new Map(), // Keyed by binding
externalVars: new Map(), // Keyed by block
scopes: undefined,
globalVarNames: new Set(),
amendments: [],
superIsProto: false,
Expand All @@ -446,6 +447,7 @@ function createFunction(id, node, isStrict, state) {

if (parentFunction) parentFunction.children.push(fn);

state.functions.push(fn);
state.fileContainsFunctionsOrEval = true;

return fn;
Expand Down Expand Up @@ -586,17 +588,16 @@ function hoistSloppyFunctionDeclaration(declaration) {
* @param {Object} fn - Function object
* @param {Object|Array} parent - Parent AST node/container
* @param {string|number} key - Node's key on parent AST node/container
* @param {string} astJson - JSON-stringified function AST
* @param {Object} paramsBlock - Function's params block object
* @param {Object} [bodyBlock] - Function's body block object (if it has one, arrow functions may not)
* @param {Object} state - State object
* @returns {undefined}
*/
function instrumentFunction(node, fn, parent, key, astJson, paramsBlock, bodyBlock, state) {
function instrumentFunction(node, fn, parent, key, paramsBlock, bodyBlock, state) {
// Restore to original place in AST
parent[key] = node;

instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, astJson, state);
instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, state);

if (node.type === 'ArrowFunctionExpression') insertArrowFunctionTrackerComment(fn, node, state);
}
Expand All @@ -611,15 +612,15 @@ function instrumentFunction(node, fn, parent, key, astJson, paramsBlock, bodyBlo
* @param {Object} fn - Function object
* @param {Object} paramsBlock - Function's params block object
* @param {Object} bodyBlock - Function's body block object
* @param {string} astJson - JSON-stringified function/class AST
* @param {Object} state - State object
* @returns {undefined}
*/
function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, astJson, state) {
// NB: `bodyBlock` here may actually be params block if is an arrow function with no body.
function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, state) {
// NB `bodyBlock` here may actually be params block if is an arrow function with no body.
// Sort scopes in ascending order of block ID.
const scopes = [...fn.externalVars].map(([block, vars]) => ({block, vars}))
.sort((scope1, scope2) => (scope1.block.id > scope2.block.id ? 1 : -1));
fn.scopes = scopes;

// Add external vars to parent function where external to that function too
const parentFunction = fn.parent;
Expand All @@ -634,13 +635,9 @@ function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock,
}
}

// Create function info function
const fnInfoVarNode = createFnInfoVarNode(fn.id, state);
createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state);

// Insert tracker call and block vars (`livepack_scopeId` and `livepack_temp`) into function.
// If function has complex params, insert into params. Otherwise, into function body.
const trackerNode = createTrackerCall(scopes, fnInfoVarNode, state);
const trackerNode = createTrackerCall(fn.id, scopes, state);

const {firstComplexParamIndex} = fn;
if (firstComplexParamIndex === undefined) {
Expand All @@ -654,15 +651,15 @@ function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock,

/**
* Create tracker call.
* @param {number} fnId - Function ID
* @param {Array<Object>} scopes - Array of scope objects in ascending order of block ID
* @param {Object} fnInfoVarNode - Function info function identifier AST node
* @param {Object} state - State object
* @returns {undefined}
*/
function createTrackerCall(scopes, fnInfoVarNode, state) {
function createTrackerCall(fnId, scopes, state) {
// `livepack_tracker(livepack_getFnInfo_3, () => [[livepack_scopeId_2, x]]);`
return t.callExpression(state.trackerVarNode, [
fnInfoVarNode,
createFnInfoVarNode(fnId, state),
t.arrowFunctionExpression([], t.arrayExpression(
scopes.map(scope => t.arrayExpression([
scope.block.varsBlock.scopeIdVarNode,
Expand Down Expand Up @@ -719,15 +716,11 @@ function insertTrackerComment(fnId, fnType, commentHolderNode, commentType, stat

/**
* Create function info function containing function AST, details of internal and external vars etc.
* Add to `state.functionInfoNodes`. It will be inserted into file at end of 2nd pass.
* @param {Object} fn - Function object
* @param {Array<Object>} scopes - Array of scope objects in ascending order of block ID
* @param {string} astJson - JSON-stringified function AST
* @param {Object} fnInfoVarNode - Function info function identifier AST node
* @param {Object} state - State object
* @returns {undefined}
* @returns {Object} - AST node for function info function
*/
function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) {
function createFunctionInfoFunction(fn, state) {
// Compile internal vars
const internalVars = Object.create(null);
for (const {varName, trails} of fn.internalVars.values()) {
Expand All @@ -739,7 +732,7 @@ function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) {
const {children} = fn;
let argNames;
let json = JSON.stringify({
scopes: scopes.map(({block, vars}) => ({
scopes: fn.scopes.map(({block, vars}) => ({
blockId: block.id,
blockName: block.name,
vars: mapValues(vars, (varProps, varName) => {
Expand Down Expand Up @@ -769,17 +762,15 @@ function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) {
});

// Add AST JSON to JSON
json = `${json.slice(0, -1)},"ast":${astJson}}`;
json = `${json.slice(0, -1)},"ast":${fn.astJson}}`;

// Create function returning JSON string and references to function info function for child fns.
// Make output as single-quoted string for shorter output (not escaping every `"`).
state.functionInfoNodes.push(
t.functionDeclaration(fnInfoVarNode, [], t.blockStatement([
t.returnStatement(t.arrayExpression([
stringLiteralWithSingleQuotes(json),
t.arrayExpression(children.map(childFn => createFnInfoVarNode(childFn.id, state))),
state.getSourcesNode
]))
return t.functionDeclaration(createFnInfoVarNode(fn.id, state), [], t.blockStatement([
t.returnStatement(t.arrayExpression([
stringLiteralWithSingleQuotes(json),
t.arrayExpression(children.map(childFn => createFnInfoVarNode(childFn.id, state))),
state.getSourcesNode
]))
);
]));
}

0 comments on commit 5faf3d4

Please sign in to comment.