3
3
import * as acorn from 'acorn' ;
4
4
import * as terser from '../third_party/terser/terser.js' ;
5
5
import * as fs from 'node:fs' ;
6
+ import assert from 'node:assert' ;
6
7
7
8
// Utilities
8
9
9
- function print ( x ) {
10
- process . stdout . write ( x + '\n' ) ;
11
- }
12
-
13
10
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' ) ;
21
12
}
22
13
23
14
function assertAt ( condition , node , message = '' ) {
24
15
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 ;
29
22
}
30
23
}
31
24
@@ -39,7 +32,7 @@ function visitChildren(node, c) {
39
32
return ;
40
33
}
41
34
function maybeChild ( child ) {
42
- if ( child && typeof child === 'object' && typeof child . type === 'string' ) {
35
+ if ( typeof child ? .type === 'string' ) {
43
36
c ( child ) ;
44
37
return true ;
45
38
}
@@ -93,22 +86,17 @@ function emptyOut(node) {
93
86
node . type = 'EmptyStatement' ;
94
87
}
95
88
96
- function isUseStrict ( node ) {
97
- return node . type === 'Literal' && node . value === 'use strict' ;
98
- }
99
-
100
89
function setLiteralValue ( item , value ) {
101
90
item . value = value ;
102
- item . raw = "'" + value + "'" ;
91
+ item . raw = null ;
103
92
}
104
93
105
94
function isLiteralString ( node ) {
106
- return node . type === 'Literal' && ( node . raw [ 0 ] === '"' || node . raw [ 0 ] === "'" ) ;
95
+ return node . type === 'Literal' && typeof node . value === 'string' ;
107
96
}
108
97
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 , ' ' ) ) ;
112
100
}
113
101
114
102
// Mark inner scopes temporarily as empty statements. Returns
@@ -117,7 +105,7 @@ function ignoreInnerScopes(node) {
117
105
const map = new WeakMap ( ) ;
118
106
function ignore ( node ) {
119
107
map . set ( node , node . type ) ;
120
- node . type = 'EmptyStatement' ;
108
+ emptyOut ( node ) ;
121
109
}
122
110
simpleWalk ( node , {
123
111
FunctionDeclaration ( node ) {
@@ -151,13 +139,17 @@ function hasSideEffects(node) {
151
139
let has = false ;
152
140
fullWalk ( node , ( node ) => {
153
141
switch ( node . type ) {
142
+ case 'ExpressionStatement' :
143
+ if ( node . directive ) {
144
+ has = true ;
145
+ }
146
+ break ;
154
147
// TODO: go through all the ESTree spec
155
148
case 'Literal' :
156
149
case 'Identifier' :
157
150
case 'UnaryExpression' :
158
151
case 'BinaryExpression' :
159
152
case 'LogicalExpression' :
160
- case 'ExpressionStatement' :
161
153
case 'UpdateOperator' :
162
154
case 'ConditionalExpression' :
163
155
case 'FunctionDeclaration' :
@@ -284,7 +276,7 @@ function JSDCE(ast, aggressive) {
284
276
}
285
277
} ,
286
278
ExpressionStatement ( node , _c ) {
287
- if ( aggressive && ! hasSideEffects ( node ) && ! isUseStrict ( node . expression ) ) {
279
+ if ( aggressive && ! hasSideEffects ( node ) ) {
288
280
emptyOut ( node ) ;
289
281
removed ++ ;
290
282
}
@@ -954,7 +946,7 @@ function emitDCEGraph(ast) {
954
946
info . reaches = sortedNamesFromMap ( info . reaches ) ;
955
947
graph . push ( info ) ;
956
948
} ) ;
957
- print ( JSON . stringify ( graph , null , ' ' ) ) ;
949
+ dump ( graph ) ;
958
950
}
959
951
960
952
// Apply graph removals from running wasm-metadce. This only removes imports and
@@ -1042,31 +1034,21 @@ function applyDCEGraphRemovals(ast) {
1042
1034
}
1043
1035
}
1044
1036
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
-
1055
1037
function createLiteral ( value ) {
1056
- return createNode ( {
1038
+ return {
1057
1039
type : 'Literal' ,
1058
1040
value : value ,
1059
1041
raw : '' + value ,
1060
- } ) ;
1042
+ } ;
1061
1043
}
1062
1044
1063
1045
function makeCallExpression ( node , name , args ) {
1064
1046
Object . assign ( node , {
1065
1047
type : 'CallExpression' ,
1066
- callee : createNode ( {
1048
+ callee : {
1067
1049
type : 'Identifier' ,
1068
1050
name : name ,
1069
- } ) ,
1051
+ } ,
1070
1052
arguments : args ,
1071
1053
} ) ;
1072
1054
}
@@ -1095,7 +1077,7 @@ function isEmscriptenHEAP(name) {
1095
1077
// LE byte order for HEAP buffer
1096
1078
function littleEndianHeap ( ast ) {
1097
1079
recursiveWalk ( ast , {
1098
- FunctionDeclaration : ( node , c ) => {
1080
+ FunctionDeclaration ( node , c ) {
1099
1081
// do not recurse into LE_HEAP_STORE, LE_HEAP_LOAD functions
1100
1082
if (
1101
1083
! (
@@ -1106,13 +1088,13 @@ function littleEndianHeap(ast) {
1106
1088
c ( node . body ) ;
1107
1089
}
1108
1090
} ,
1109
- VariableDeclarator : ( node , c ) => {
1091
+ VariableDeclarator ( node , c ) {
1110
1092
if ( ! ( node . id . type === 'Identifier' && node . id . name . startsWith ( 'LE_ATOMICS_' ) ) ) {
1111
1093
c ( node . id ) ;
1112
1094
if ( node . init ) c ( node . init ) ;
1113
1095
}
1114
1096
} ,
1115
- AssignmentExpression : ( node , c ) => {
1097
+ AssignmentExpression ( node , c ) {
1116
1098
const target = node . left ;
1117
1099
const value = node . right ;
1118
1100
c ( value ) ;
@@ -1172,7 +1154,7 @@ function littleEndianHeap(ast) {
1172
1154
}
1173
1155
}
1174
1156
} ,
1175
- CallExpression : ( node , c ) => {
1157
+ CallExpression ( node , c ) {
1176
1158
if ( node . arguments ) {
1177
1159
for ( var a of node . arguments ) c ( a ) ;
1178
1160
}
@@ -1181,7 +1163,6 @@ function littleEndianHeap(ast) {
1181
1163
node . callee . type === 'MemberExpression' &&
1182
1164
node . callee . object . type === 'Identifier' &&
1183
1165
node . callee . object . name === 'Atomics' &&
1184
- node . callee . property . type === 'Identifier' &&
1185
1166
! node . callee . computed
1186
1167
) {
1187
1168
makeCallExpression (
@@ -1193,7 +1174,7 @@ function littleEndianHeap(ast) {
1193
1174
c ( node . callee ) ;
1194
1175
}
1195
1176
} ,
1196
- MemberExpression : ( node , c ) => {
1177
+ MemberExpression ( node , c ) {
1197
1178
c ( node . property ) ;
1198
1179
if ( ! isHEAPAccess ( node ) ) {
1199
1180
// not accessing the HEAP
@@ -1269,39 +1250,37 @@ function growableHeap(ast) {
1269
1250
c ( node . body ) ;
1270
1251
}
1271
1252
} ,
1272
- AssignmentExpression : ( node ) => {
1253
+ AssignmentExpression ( node ) {
1273
1254
if ( node . left . type !== 'Identifier' ) {
1274
1255
// Don't transform `HEAPxx =` assignments.
1275
1256
growableHeap ( node . left ) ;
1276
1257
}
1277
1258
growableHeap ( node . right ) ;
1278
1259
} ,
1279
- VariableDeclaration : ( node ) => {
1260
+ VariableDeclarator ( node ) {
1280
1261
// 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
+ }
1288
1267
} ,
1289
- Identifier : ( node ) => {
1268
+ Identifier ( node ) {
1290
1269
if ( isEmscriptenHEAP ( node . name ) ) {
1291
1270
// Transform `HEAPxx` into `(growMemViews(), HEAPxx)`.
1292
1271
// Important: don't just do `growMemViews(HEAPxx)` because `growMemViews` reassigns `HEAPxx`
1293
1272
// and we want to get an updated value after that reassignment.
1294
1273
Object . assign ( node , {
1295
1274
type : 'SequenceExpression' ,
1296
1275
expressions : [
1297
- createNode ( {
1276
+ {
1298
1277
type : 'CallExpression' ,
1299
- callee : createNode ( {
1278
+ callee : {
1300
1279
type : 'Identifier' ,
1301
1280
name : 'growMemViews' ,
1302
- } ) ,
1281
+ } ,
1303
1282
arguments : [ ] ,
1304
- } ) ,
1283
+ } ,
1305
1284
{ ...node } ,
1306
1285
] ,
1307
1286
} ) ;
@@ -1337,12 +1316,7 @@ function unsignPointers(ast) {
1337
1316
right : {
1338
1317
type : 'Literal' ,
1339
1318
value : 0 ,
1340
- raw : '0' ,
1341
- start : 0 ,
1342
- end : 0 ,
1343
1319
} ,
1344
- start : 0 ,
1345
- end : 0 ,
1346
1320
} ;
1347
1321
}
1348
1322
@@ -1357,7 +1331,6 @@ function unsignPointers(ast) {
1357
1331
node . callee . type === 'MemberExpression' &&
1358
1332
node . callee . object . type === 'Identifier' &&
1359
1333
isHeap ( node . callee . object . name ) &&
1360
- node . callee . property . type === 'Identifier' &&
1361
1334
! node . callee . computed
1362
1335
) {
1363
1336
// This is a call on HEAP*.?. Specific things we need to fix up are
@@ -1430,12 +1403,12 @@ function asanify(ast) {
1430
1403
}
1431
1404
1432
1405
function multiply ( value , by ) {
1433
- return createNode ( {
1406
+ return {
1434
1407
type : 'BinaryExpression' ,
1435
1408
left : value ,
1436
1409
operator : '*' ,
1437
1410
right : createLiteral ( by ) ,
1438
- } ) ;
1411
+ } ;
1439
1412
}
1440
1413
1441
1414
// Replace direct heap access with SAFE_HEAP* calls.
@@ -1523,7 +1496,7 @@ function ensureMinifiedNames(n) {
1523
1496
1524
1497
function minifyLocals ( ast ) {
1525
1498
// We are given a mapping of global names to their minified forms.
1526
- assert ( extraInfo && extraInfo . globals ) ;
1499
+ assert ( extraInfo ? .globals ) ;
1527
1500
1528
1501
for ( const fun of ast . body ) {
1529
1502
if ( fun . type !== 'FunctionDeclaration' ) {
@@ -1787,7 +1760,7 @@ function reattachComments(ast, commentsMap) {
1787
1760
// Collect all code symbols
1788
1761
ast . walk (
1789
1762
new terser . TreeWalker ( ( node ) => {
1790
- if ( node . start && node . start . pos ) {
1763
+ if ( node . start ? .pos ) {
1791
1764
symbols . push ( node ) ;
1792
1765
}
1793
1766
} ) ,
@@ -1853,11 +1826,6 @@ function trace(...args) {
1853
1826
}
1854
1827
}
1855
1828
1856
- function error ( ...args ) {
1857
- console . error ( ...args ) ;
1858
- throw new Error ( ...args ) ;
1859
- }
1860
-
1861
1829
// If enabled, output retains parentheses and comments so that the
1862
1830
// output can further be passed out to Closure.
1863
1831
const closureFriendly = getArg ( '--closure-friendly' ) ;
@@ -1894,29 +1862,14 @@ if (closureFriendly) {
1894
1862
const currentComments = [ ] ;
1895
1863
Object . assign ( params , {
1896
1864
preserveParens : true ,
1897
- onToken : ( token ) => {
1865
+ onToken ( token ) {
1898
1866
// Associate comments with the start position of the next token.
1899
1867
sourceComments [ token . start ] = currentComments . slice ( ) ;
1900
1868
currentComments . length = 0 ;
1901
1869
} ,
1902
1870
onComment : currentComments ,
1903
1871
} ) ;
1904
1872
}
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
- }
1920
1873
1921
1874
const registry = {
1922
1875
JSDCE ,
@@ -1934,13 +1887,23 @@ const registry = {
1934
1887
minifyGlobals,
1935
1888
} ;
1936
1889
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 } ` ;
1941
1904
}
1942
- registry [ pass ] ( ast ) ;
1943
- } ) ;
1905
+ throw err ;
1906
+ }
1944
1907
1945
1908
if ( ! noPrint ) {
1946
1909
const terserAst = terser . AST_Node . from_mozilla_ast ( ast ) ;
0 commit comments