Skip to content

Commit 94725cc

Browse files
authored
Minor acorn-optimizer simplifications. NFC (#24348)
- Remove unused argument from `dump`. - Use coalescing operator where possible. - Remove usage of the `Node` class from acorn - it's not necessary as ESTree tools only care about plain JS properties and don't care about subclassing. - Remove optional no-op properties in created nodes like `raw`, `start`, `end`. - Remove unnecessary checks for both `!memberExpr.computed` and `memberExpr.property.type === 'Identifier'` - the former implies the latter. - Simplify directive check for 'use strict' and the like. - Use native Node APIs instead of helpers where possible. - Use shorter ES6+ syntax where possible. - Unify `assertAt` errors format with parsing errors. - Target a narrower `VariableDeclarator` instead of `VariableDeclaration` where we don't care about the parent.
1 parent a5b003d commit 94725cc

File tree

2 files changed

+64
-102
lines changed

2 files changed

+64
-102
lines changed

test/test_other.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10497,8 +10497,7 @@ def test_js_optimizer_parse_error(self):
1049710497
stderr = self.expect_fail([EMCC, 'src.c', '-O2'] + self.get_emcc_args())
1049810498
self.assertContained(('''
1049910499
function js() { var x = !<->5.; }
10500-
^
10501-
'''), stderr)
10500+
^ '''), stderr)
1050210501

1050310502
@crossplatform
1050410503
def test_js_optimizer_chunk_size_determinism(self):

tools/acorn-optimizer.mjs

Lines changed: 63 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,22 @@
33
import * as acorn from 'acorn';
44
import * as terser from '../third_party/terser/terser.js';
55
import * as fs from 'node:fs';
6+
import assert from 'node:assert';
67

78
// Utilities
89

9-
function print(x) {
10-
process.stdout.write(x + '\n');
11-
}
12-
1310
function read(x) {
14-
return fs.readFileSync(x).toString();
15-
}
16-
17-
function assert(condition, text) {
18-
if (!condition) {
19-
throw new Error(text);
20-
}
11+
return fs.readFileSync(x, 'utf-8');
2112
}
2213

2314
function assertAt(condition, node, message = '') {
2415
if (!condition) {
25-
const loc = acorn.getLineInfo(input, node.start);
26-
throw new Error(
27-
`${infile}:${loc.line}: ${message} (use EMCC_DEBUG_SAVE=1 to preserve temporary inputs)`,
28-
);
16+
if (!process.env.EMCC_DEBUG_SAVE) {
17+
message += ' (use EMCC_DEBUG_SAVE=1 to preserve temporary inputs)';
18+
}
19+
let err = new Error(message);
20+
err['loc'] = acorn.getLineInfo(input, node.start);
21+
throw err;
2922
}
3023
}
3124

@@ -39,7 +32,7 @@ function visitChildren(node, c) {
3932
return;
4033
}
4134
function maybeChild(child) {
42-
if (child && typeof child === 'object' && typeof child.type === 'string') {
35+
if (typeof child?.type === 'string') {
4336
c(child);
4437
return true;
4538
}
@@ -93,22 +86,17 @@ function emptyOut(node) {
9386
node.type = 'EmptyStatement';
9487
}
9588

96-
function isUseStrict(node) {
97-
return node.type === 'Literal' && node.value === 'use strict';
98-
}
99-
10089
function setLiteralValue(item, value) {
10190
item.value = value;
102-
item.raw = "'" + value + "'";
91+
item.raw = null;
10392
}
10493

10594
function isLiteralString(node) {
106-
return node.type === 'Literal' && (node.raw[0] === '"' || node.raw[0] === "'");
95+
return node.type === 'Literal' && typeof node.value === 'string';
10796
}
10897

109-
function dump(node, text) {
110-
if (text) print(text);
111-
print(JSON.stringify(node, null, ' '));
98+
function dump(node) {
99+
console.log(JSON.stringify(node, null, ' '));
112100
}
113101

114102
// Mark inner scopes temporarily as empty statements. Returns
@@ -117,7 +105,7 @@ function ignoreInnerScopes(node) {
117105
const map = new WeakMap();
118106
function ignore(node) {
119107
map.set(node, node.type);
120-
node.type = 'EmptyStatement';
108+
emptyOut(node);
121109
}
122110
simpleWalk(node, {
123111
FunctionDeclaration(node) {
@@ -151,13 +139,17 @@ function hasSideEffects(node) {
151139
let has = false;
152140
fullWalk(node, (node) => {
153141
switch (node.type) {
142+
case 'ExpressionStatement':
143+
if (node.directive) {
144+
has = true;
145+
}
146+
break;
154147
// TODO: go through all the ESTree spec
155148
case 'Literal':
156149
case 'Identifier':
157150
case 'UnaryExpression':
158151
case 'BinaryExpression':
159152
case 'LogicalExpression':
160-
case 'ExpressionStatement':
161153
case 'UpdateOperator':
162154
case 'ConditionalExpression':
163155
case 'FunctionDeclaration':
@@ -284,7 +276,7 @@ function JSDCE(ast, aggressive) {
284276
}
285277
},
286278
ExpressionStatement(node, _c) {
287-
if (aggressive && !hasSideEffects(node) && !isUseStrict(node.expression)) {
279+
if (aggressive && !hasSideEffects(node)) {
288280
emptyOut(node);
289281
removed++;
290282
}
@@ -954,7 +946,7 @@ function emitDCEGraph(ast) {
954946
info.reaches = sortedNamesFromMap(info.reaches);
955947
graph.push(info);
956948
});
957-
print(JSON.stringify(graph, null, ' '));
949+
dump(graph);
958950
}
959951

960952
// Apply graph removals from running wasm-metadce. This only removes imports and
@@ -1042,31 +1034,21 @@ function applyDCEGraphRemovals(ast) {
10421034
}
10431035
}
10441036

1045-
// Need a parser to pass to acorn.Node constructor.
1046-
// Create it once and reuse it.
1047-
const stubParser = new acorn.Parser({ecmaVersion: 2021});
1048-
1049-
function createNode(props) {
1050-
const node = new acorn.Node(stubParser);
1051-
Object.assign(node, props);
1052-
return node;
1053-
}
1054-
10551037
function createLiteral(value) {
1056-
return createNode({
1038+
return {
10571039
type: 'Literal',
10581040
value: value,
10591041
raw: '' + value,
1060-
});
1042+
};
10611043
}
10621044

10631045
function makeCallExpression(node, name, args) {
10641046
Object.assign(node, {
10651047
type: 'CallExpression',
1066-
callee: createNode({
1048+
callee: {
10671049
type: 'Identifier',
10681050
name: name,
1069-
}),
1051+
},
10701052
arguments: args,
10711053
});
10721054
}
@@ -1095,7 +1077,7 @@ function isEmscriptenHEAP(name) {
10951077
// LE byte order for HEAP buffer
10961078
function littleEndianHeap(ast) {
10971079
recursiveWalk(ast, {
1098-
FunctionDeclaration: (node, c) => {
1080+
FunctionDeclaration(node, c) {
10991081
// do not recurse into LE_HEAP_STORE, LE_HEAP_LOAD functions
11001082
if (
11011083
!(
@@ -1106,13 +1088,13 @@ function littleEndianHeap(ast) {
11061088
c(node.body);
11071089
}
11081090
},
1109-
VariableDeclarator: (node, c) => {
1091+
VariableDeclarator(node, c) {
11101092
if (!(node.id.type === 'Identifier' && node.id.name.startsWith('LE_ATOMICS_'))) {
11111093
c(node.id);
11121094
if (node.init) c(node.init);
11131095
}
11141096
},
1115-
AssignmentExpression: (node, c) => {
1097+
AssignmentExpression(node, c) {
11161098
const target = node.left;
11171099
const value = node.right;
11181100
c(value);
@@ -1172,7 +1154,7 @@ function littleEndianHeap(ast) {
11721154
}
11731155
}
11741156
},
1175-
CallExpression: (node, c) => {
1157+
CallExpression(node, c) {
11761158
if (node.arguments) {
11771159
for (var a of node.arguments) c(a);
11781160
}
@@ -1181,7 +1163,6 @@ function littleEndianHeap(ast) {
11811163
node.callee.type === 'MemberExpression' &&
11821164
node.callee.object.type === 'Identifier' &&
11831165
node.callee.object.name === 'Atomics' &&
1184-
node.callee.property.type === 'Identifier' &&
11851166
!node.callee.computed
11861167
) {
11871168
makeCallExpression(
@@ -1193,7 +1174,7 @@ function littleEndianHeap(ast) {
11931174
c(node.callee);
11941175
}
11951176
},
1196-
MemberExpression: (node, c) => {
1177+
MemberExpression(node, c) {
11971178
c(node.property);
11981179
if (!isHEAPAccess(node)) {
11991180
// not accessing the HEAP
@@ -1269,39 +1250,37 @@ function growableHeap(ast) {
12691250
c(node.body);
12701251
}
12711252
},
1272-
AssignmentExpression: (node) => {
1253+
AssignmentExpression(node) {
12731254
if (node.left.type !== 'Identifier') {
12741255
// Don't transform `HEAPxx =` assignments.
12751256
growableHeap(node.left);
12761257
}
12771258
growableHeap(node.right);
12781259
},
1279-
VariableDeclaration: (node) => {
1260+
VariableDeclarator(node) {
12801261
// Don't transform the var declarations for HEAP8 etc
1281-
node.declarations.forEach((decl) => {
1282-
// but do transform anything that sets a var to
1283-
// something from HEAP8 etc
1284-
if (decl.init) {
1285-
growableHeap(decl.init);
1286-
}
1287-
});
1262+
// but do transform anything that sets a var to
1263+
// something from HEAP8 etc
1264+
if (node.init) {
1265+
growableHeap(node.init);
1266+
}
12881267
},
1289-
Identifier: (node) => {
1268+
Identifier(node) {
12901269
if (isEmscriptenHEAP(node.name)) {
12911270
// Transform `HEAPxx` into `(growMemViews(), HEAPxx)`.
12921271
// Important: don't just do `growMemViews(HEAPxx)` because `growMemViews` reassigns `HEAPxx`
12931272
// and we want to get an updated value after that reassignment.
12941273
Object.assign(node, {
12951274
type: 'SequenceExpression',
12961275
expressions: [
1297-
createNode({
1276+
{
12981277
type: 'CallExpression',
1299-
callee: createNode({
1278+
callee: {
13001279
type: 'Identifier',
13011280
name: 'growMemViews',
1302-
}),
1281+
},
13031282
arguments: [],
1304-
}),
1283+
},
13051284
{...node},
13061285
],
13071286
});
@@ -1337,12 +1316,7 @@ function unsignPointers(ast) {
13371316
right: {
13381317
type: 'Literal',
13391318
value: 0,
1340-
raw: '0',
1341-
start: 0,
1342-
end: 0,
13431319
},
1344-
start: 0,
1345-
end: 0,
13461320
};
13471321
}
13481322

@@ -1357,7 +1331,6 @@ function unsignPointers(ast) {
13571331
node.callee.type === 'MemberExpression' &&
13581332
node.callee.object.type === 'Identifier' &&
13591333
isHeap(node.callee.object.name) &&
1360-
node.callee.property.type === 'Identifier' &&
13611334
!node.callee.computed
13621335
) {
13631336
// This is a call on HEAP*.?. Specific things we need to fix up are
@@ -1430,12 +1403,12 @@ function asanify(ast) {
14301403
}
14311404

14321405
function multiply(value, by) {
1433-
return createNode({
1406+
return {
14341407
type: 'BinaryExpression',
14351408
left: value,
14361409
operator: '*',
14371410
right: createLiteral(by),
1438-
});
1411+
};
14391412
}
14401413

14411414
// Replace direct heap access with SAFE_HEAP* calls.
@@ -1523,7 +1496,7 @@ function ensureMinifiedNames(n) {
15231496

15241497
function minifyLocals(ast) {
15251498
// We are given a mapping of global names to their minified forms.
1526-
assert(extraInfo && extraInfo.globals);
1499+
assert(extraInfo?.globals);
15271500

15281501
for (const fun of ast.body) {
15291502
if (fun.type !== 'FunctionDeclaration') {
@@ -1787,7 +1760,7 @@ function reattachComments(ast, commentsMap) {
17871760
// Collect all code symbols
17881761
ast.walk(
17891762
new terser.TreeWalker((node) => {
1790-
if (node.start && node.start.pos) {
1763+
if (node.start?.pos) {
17911764
symbols.push(node);
17921765
}
17931766
}),
@@ -1853,11 +1826,6 @@ function trace(...args) {
18531826
}
18541827
}
18551828

1856-
function error(...args) {
1857-
console.error(...args);
1858-
throw new Error(...args);
1859-
}
1860-
18611829
// If enabled, output retains parentheses and comments so that the
18621830
// output can further be passed out to Closure.
18631831
const closureFriendly = getArg('--closure-friendly');
@@ -1894,29 +1862,14 @@ if (closureFriendly) {
18941862
const currentComments = [];
18951863
Object.assign(params, {
18961864
preserveParens: true,
1897-
onToken: (token) => {
1865+
onToken(token) {
18981866
// Associate comments with the start position of the next token.
18991867
sourceComments[token.start] = currentComments.slice();
19001868
currentComments.length = 0;
19011869
},
19021870
onComment: currentComments,
19031871
});
19041872
}
1905-
let ast;
1906-
try {
1907-
ast = acorn.parse(input, params);
1908-
} catch (err) {
1909-
err.message += (() => {
1910-
let errorMessage = '\n' + input.split(acorn.lineBreak)[err.loc.line - 1] + '\n';
1911-
let column = err.loc.column;
1912-
while (column--) {
1913-
errorMessage += ' ';
1914-
}
1915-
errorMessage += '^\n';
1916-
return errorMessage;
1917-
})();
1918-
throw err;
1919-
}
19201873

19211874
const registry = {
19221875
JSDCE,
@@ -1934,13 +1887,23 @@ const registry = {
19341887
minifyGlobals,
19351888
};
19361889

1937-
passes.forEach((pass) => {
1938-
trace(`running AST pass: ${pass}`);
1939-
if (!(pass in registry)) {
1940-
error(`unknown optimizer pass: ${pass}`);
1890+
let ast;
1891+
try {
1892+
ast = acorn.parse(input, params);
1893+
for (let pass of passes) {
1894+
const resolvedPass = registry[pass];
1895+
assert(resolvedPass, `unknown optimizer pass: ${pass}`);
1896+
resolvedPass(ast);
1897+
}
1898+
} catch (err) {
1899+
if (err.loc) {
1900+
err.message +=
1901+
'\n' +
1902+
`${input.split(acorn.lineBreak)[err.loc.line - 1]}\n` +
1903+
`${' '.repeat(err.loc.column)}^ ${infile}:${err.loc.line}:${err.loc.column + 1}`;
19411904
}
1942-
registry[pass](ast);
1943-
});
1905+
throw err;
1906+
}
19441907

19451908
if (!noPrint) {
19461909
const terserAst = terser.AST_Node.from_mozilla_ast(ast);

0 commit comments

Comments
 (0)