@@ -89,14 +89,6 @@ function emptyOut(node) {
89
89
node . type = 'EmptyStatement' ;
90
90
}
91
91
92
- // This converts the node into something that terser will ignore in a var
93
- // declaration, that is, it is a way to get rid of initial values.
94
- function convertToNothingInVarInit ( node ) {
95
- node . type = 'Literal' ;
96
- node . value = undefined ;
97
- node . raw = 'undefined' ;
98
- }
99
-
100
92
function convertToNullStatement ( node ) {
101
93
node . type = 'ExpressionStatement' ;
102
94
node . expression = {
@@ -591,18 +583,25 @@ function isDynamicDynCall(node) {
591
583
592
584
//
593
585
// Matches the wasm export wrappers generated by emcc (see make_export_wrappers
594
- // in emscripten.py). For example:
586
+ // in emscripten.py). For example, the right hand side of these assignments :
595
587
//
596
588
// var _foo = (a0, a1) => (_foo = wasmExports['foo'])(a0, a1):
597
589
//
590
+ // or
591
+ //
592
+ // var _foo = (a0, a1) => (_foo = Module['_foo'] = wasmExports['foo'])(a0, a1):
593
+ //
598
594
function isExportWrapperFunction ( f ) {
599
595
if ( f . body . type != 'CallExpression' ) return null ;
600
596
let callee = f . body . callee ;
601
597
if ( callee . type == 'ParenthesizedExpression' ) {
602
598
callee = callee . expression ;
603
599
}
604
- if ( callee . type != 'AssignmentExpression' || callee . right . type != 'MemberExpression' ) return null ;
600
+ if ( callee . type != 'AssignmentExpression' ) return null ;
605
601
var rhs = callee . right ;
602
+ if ( rhs . type == 'AssignmentExpression' ) {
603
+ rhs = rhs . right ;
604
+ }
606
605
if ( rhs . type != 'MemberExpression' || ! isExportUse ( rhs ) ) return null ;
607
606
return getExportOrModuleUseName ( rhs ) ;
608
607
}
@@ -740,7 +739,9 @@ function emitDCEGraph(ast) {
740
739
emptyOut ( node ) ;
741
740
} else if ( value && value . type === 'ArrowFunctionExpression' ) {
742
741
// this is
743
- // var x = () => (x = wasmExports['x'])(..)
742
+ // () => (x = wasmExports['x'])(..)
743
+ // or
744
+ // () => (x = Module['_x'] = wasmExports['x'])(..)
744
745
let asmName = isExportWrapperFunction ( value ) ;
745
746
if ( asmName ) {
746
747
saveAsmExport ( name , asmName ) ;
@@ -978,67 +979,84 @@ function emitDCEGraph(ast) {
978
979
// way (and we leave further DCE on the JS and wasm sides to their respective
979
980
// optimizers, closure compiler and binaryen).
980
981
function applyDCEGraphRemovals ( ast ) {
981
- const unused = new Set ( extraInfo . unused ) ;
982
+ const unusedExports = new Set ( extraInfo . unusedExports ) ;
983
+ const unusedImports = new Set ( extraInfo . unusedImports ) ;
984
+ const foundUnusedImports = new Set ( ) ;
985
+ const foundUnusedExports = new Set ( ) ;
986
+ trace ( 'unusedExports:' , unusedExports ) ;
987
+ trace ( 'unusedImports:' , unusedImports ) ;
982
988
983
989
fullWalk ( ast , ( node ) => {
984
990
if ( isWasmImportsAssign ( node ) ) {
985
991
const assignedObject = getWasmImportsValue ( node ) ;
986
992
assignedObject . properties = assignedObject . properties . filter ( ( item ) => {
987
993
const name = item . key . name ;
988
994
const value = item . value ;
989
- const full = 'emcc$import$' + name ;
990
- return ! ( unused . has ( full ) && ! hasSideEffects ( value ) ) ;
991
- } ) ;
992
- } else if ( node . type === 'AssignmentExpression' ) {
993
- // when we assign to a thing we don't need, we can just remove the assign
994
- // var x = Module['x'] = wasmExports['x'];
995
- const target = node . left ;
996
- if ( isExportUse ( target ) || isModuleUse ( target ) ) {
997
- const name = getExportOrModuleUseName ( target ) ;
998
- const full = 'emcc$export$' + name ;
999
- const value = node . right ;
1000
- if ( unused . has ( full ) && ( isExportUse ( value ) || ! hasSideEffects ( value ) ) ) {
1001
- // This will be in a var init, and we just remove that value.
1002
- convertToNothingInVarInit ( node ) ;
995
+ if ( unusedImports . has ( name ) ) {
996
+ foundUnusedImports . add ( name ) ;
997
+ return hasSideEffects ( value ) ;
1003
998
}
1004
- }
999
+ return true ;
1000
+ } ) ;
1005
1001
} else if ( node . type === 'VariableDeclaration' ) {
1006
- // Handle the case we declare a variable but don't assign to the module:
1007
- // var x = wasmExports['x'];
1008
- // and
1009
- // var x = () => (x = wasmExports['x']).apply(...);
1002
+ // Handle the various ways in which we extract wasmExports:
1003
+ // 1. var _x = wasmExports['x'];
1004
+ // or
1005
+ // 2. var _x = Module['_x'] = wasmExports['x'];
1006
+ //
1007
+ // Or for delayed instantiation:
1008
+ // 3. var _x = () => (_x = wasmExports['x'])(...);
1009
+ // or
1010
+ // 4. var _x = Module['_x] = () => (_x = Module['_x'] = wasmExports['x'])(...);
1010
1011
const init = node . declarations [ 0 ] . init ;
1011
- if ( init ) {
1012
- if ( isExportUse ( init ) ) {
1013
- const name = getExportOrModuleUseName ( init ) ;
1014
- const full = 'emcc$export$' + name ;
1015
- if ( unused . has ( full ) ) {
1016
- convertToNothingInVarInit ( init ) ;
1017
- }
1018
- } else if ( init . type == 'ArrowFunctionExpression' ) {
1019
- const name = isExportWrapperFunction ( init ) ;
1020
- const full = 'emcc$export$' + name ;
1021
- if ( unused . has ( full ) ) {
1022
- convertToNothingInVarInit ( init ) ;
1023
- }
1012
+ if ( ! init ) {
1013
+ return ;
1014
+ }
1015
+
1016
+ // Look through the optional `Module['_x']`
1017
+ let realInit = init ;
1018
+ if ( init . type == 'AssignmentExpression' && isModuleUse ( init . left ) ) {
1019
+ realInit = init . right ;
1020
+ }
1021
+
1022
+ if ( isExportUse ( realInit ) ) {
1023
+ const export_name = getExportOrModuleUseName ( realInit ) ;
1024
+ if ( unusedExports . has ( export_name ) ) {
1025
+ // Case (1) and (2)
1026
+ trace ( 'found unused export:' , export_name ) ;
1027
+ emptyOut ( node ) ;
1028
+ foundUnusedExports . add ( export_name ) ;
1029
+ }
1030
+ } else if ( realInit . type == 'ArrowFunctionExpression' ) {
1031
+ const export_name = isExportWrapperFunction ( realInit ) ;
1032
+ if ( unusedExports . has ( export_name ) ) {
1033
+ // Case (3) and (4)
1034
+ trace ( 'found unused export:' , export_name ) ;
1035
+ emptyOut ( node ) ;
1036
+ foundUnusedExports . add ( export_name ) ;
1024
1037
}
1025
1038
}
1026
1039
} else if ( node . type === 'ExpressionStatement' ) {
1027
1040
const expr = node . expression ;
1028
1041
// In the MINIMAL_RUNTIME code pattern we have just
1029
- // x = wasmExports['x']
1042
+ // _x = wasmExports['x']
1030
1043
// and never in a var.
1031
1044
if ( expr . operator === '=' && expr . left . type === 'Identifier' && isExportUse ( expr . right ) ) {
1032
- const name = expr . left . name ;
1033
- if ( name === getExportOrModuleUseName ( expr . right ) ) {
1034
- const full = 'emcc$export$' + name ;
1035
- if ( unused . has ( full ) ) {
1036
- emptyOut ( node ) ;
1037
- }
1045
+ const export_name = getExportOrModuleUseName ( expr . right ) ;
1046
+ if ( unusedExports . has ( export_name ) ) {
1047
+ emptyOut ( node ) ;
1048
+ foundUnusedExports . add ( export_name ) ;
1038
1049
}
1039
1050
}
1040
1051
}
1041
1052
} ) ;
1053
+
1054
+ for ( const i of unusedImports ) {
1055
+ assert ( foundUnusedImports . has ( i ) , 'unused import not found: ' + i ) ;
1056
+ }
1057
+ for ( const e of unusedExports ) {
1058
+ assert ( foundUnusedExports . has ( e ) , 'unused export not found: ' + e ) ;
1059
+ }
1042
1060
}
1043
1061
1044
1062
// Need a parser to pass to acorn.Node constructor.
@@ -2050,7 +2068,10 @@ const registry = {
2050
2068
minifyGlobals : minifyGlobals ,
2051
2069
} ;
2052
2070
2053
- passes . forEach ( ( pass ) => registry [ pass ] ( ast ) ) ;
2071
+ passes . forEach ( ( pass ) => {
2072
+ trace ( `running AST pass: ${ pass } ` ) ;
2073
+ registry [ pass ] ( ast ) ;
2074
+ } ) ;
2054
2075
2055
2076
if ( ! noPrint ) {
2056
2077
const terserAst = terser . AST_Node . from_mozilla_ast ( ast ) ;
0 commit comments