@@ -51,16 +51,31 @@ struct MetaDCEGraph {
5151 std::unordered_map<Name, DCENode> nodes;
5252 std::unordered_set<Name> roots;
5353
54- std::unordered_map<Name, Name> importToDCENode; // import internal name => DCE name
5554 std::unordered_map<Name, Name> exportToDCENode; // export exported name => DCE name
5655 std::unordered_map<Name, Name> functionToDCENode; // function name => DCE name
5756 std::unordered_map<Name, Name> globalToDCENode; // global name => DCE name
5857
59- std::unordered_map<Name, Name> DCENodeToImport; // reverse maps
60- std::unordered_map<Name, Name> DCENodeToExport;
58+ std::unordered_map<Name, Name> DCENodeToExport; // reverse maps
6159 std::unordered_map<Name, Name> DCENodeToFunction;
6260 std::unordered_map<Name, Name> DCENodeToGlobal;
6361
62+ // imports are not mapped 1:1 to DCE nodes in the wasm, since env.X might
63+ // be imported twice, for example. So we don't map a DCE node to an Import,
64+ // but rather the module.base pair ("id") for the import.
65+ // TODO: implement this in a safer way, not a string with a magic separator
66+ typedef Name ImportId;
67+
68+ ImportId getImportId (Name module , Name base) {
69+ return std::string (module .str ) + " (*) " + std::string (base.str );
70+ }
71+
72+ ImportId getImportId (Name name) {
73+ auto * imp = wasm.getImport (name);
74+ return getImportId (imp->module , imp->base );
75+ }
76+
77+ std::unordered_map<Name, Name> importIdToDCENode; // import module.base => DCE name
78+
6479 Module& wasm;
6580
6681 MetaDCEGraph (Module& wasm) : wasm(wasm) {}
@@ -84,11 +99,13 @@ struct MetaDCEGraph {
8499 nodes[dceName] = DCENode (dceName);
85100 }
86101 for (auto & imp : wasm.imports ) {
87- if (importToDCENode.find (imp->name ) == importToDCENode.end ()) {
88- auto dceName = getName (" import" , imp->name .str );
89- DCENodeToImport[dceName] = imp->name ;
90- importToDCENode[imp->name ] = dceName;
91- nodes[dceName] = DCENode (dceName);
102+ // only process function and global imports - the table and memory are always there
103+ if (imp->kind == ExternalKind::Function || imp->kind == ExternalKind::Global) {
104+ auto id = getImportId (imp->module , imp->base );
105+ if (importIdToDCENode.find (id) == importIdToDCENode.end ()) {
106+ auto dceName = getName (" importId" , imp->name .str );
107+ importIdToDCENode[id] = dceName;
108+ }
92109 }
93110 }
94111 for (auto & exp : wasm.exports ) {
@@ -104,16 +121,72 @@ struct MetaDCEGraph {
104121 if (wasm.getFunctionOrNull (exp->value )) {
105122 node.reaches .push_back (functionToDCENode[exp->value ]);
106123 } else {
107- node.reaches .push_back (importToDCENode[ exp->value ]);
124+ node.reaches .push_back (importIdToDCENode[ getImportId ( exp->value ) ]);
108125 }
109126 } else if (exp->kind == ExternalKind::Global) {
110127 if (wasm.getGlobalOrNull (exp->value )) {
111128 node.reaches .push_back (globalToDCENode[exp->value ]);
112129 } else {
113- node.reaches .push_back (importToDCENode[ exp->value ]);
130+ node.reaches .push_back (importIdToDCENode[ getImportId ( exp->value ) ]);
114131 }
115132 }
116133 }
134+ // Add initializer dependencies
135+ // if we provide a parent DCE name, that is who can reach what we see
136+ // if none is provided, then it is something we must root
137+ struct InitScanner : public PostWalker <InitScanner> {
138+ InitScanner (MetaDCEGraph* parent, Name parentDceName) : parent(parent), parentDceName(parentDceName) {}
139+
140+ void visitGetGlobal (GetGlobal* curr) {
141+ handleGlobal (curr->name );
142+ }
143+ void visitSetGlobal (SetGlobal* curr) {
144+ handleGlobal (curr->name );
145+ }
146+
147+ private:
148+ MetaDCEGraph* parent;
149+ Name parentDceName;
150+
151+ void handleGlobal (Name name) {
152+ Name dceName;
153+ if (getModule ()->getGlobalOrNull (name)) {
154+ // its a global
155+ dceName = parent->globalToDCENode [name];
156+ } else {
157+ // it's an import.
158+ dceName = parent->importIdToDCENode [parent->getImportId (name)];
159+ }
160+ if (parentDceName.isNull ()) {
161+ parent->roots .insert (parentDceName);
162+ } else {
163+ parent->nodes [parentDceName].reaches .push_back (dceName);
164+ }
165+ }
166+ };
167+ for (auto & global : wasm.globals ) {
168+ InitScanner scanner (this , globalToDCENode[global->name ]);
169+ scanner.setModule (&wasm);
170+ scanner.walk (global->init );
171+ }
172+ // we can't remove segments, so root what they need
173+ InitScanner rooter (this , Name ());
174+ rooter.setModule (&wasm);
175+ for (auto & segment : wasm.table .segments ) {
176+ // TODO: currently, all functions in the table are roots, but we
177+ // should add an option to refine that
178+ for (auto & name : segment.data ) {
179+ if (wasm.getFunctionOrNull (name)) {
180+ roots.insert (functionToDCENode[name]);
181+ } else {
182+ roots.insert (importIdToDCENode[getImportId (name)]);
183+ }
184+ }
185+ rooter.walk (segment.offset );
186+ }
187+ for (auto & segment : wasm.memory .segments ) {
188+ rooter.walk (segment.offset );
189+ }
117190
118191 // A parallel scanner for function bodies
119192 struct Scanner : public WalkerPass <PostWalker<Scanner>> {
@@ -133,7 +206,7 @@ struct MetaDCEGraph {
133206 void visitCallImport (CallImport* curr) {
134207 assert (parent->functionToDCENode .count (getFunction ()->name ) > 0 );
135208 parent->nodes [parent->functionToDCENode [getFunction ()->name ]].reaches .push_back (
136- parent->importToDCENode [ curr->target ]
209+ parent->importIdToDCENode [parent-> getImportId ( curr->target ) ]
137210 );
138211 }
139212 void visitGetGlobal (GetGlobal* curr) {
@@ -147,33 +220,23 @@ struct MetaDCEGraph {
147220 MetaDCEGraph* parent;
148221
149222 void handleGlobal (Name name) {
150- if (getModule ()->getGlobalOrNull (name)) return ;
151- // it's an import
152- parent->nodes [parent->functionToDCENode [getFunction ()->name ]].reaches .push_back (
153- parent->importToDCENode [name]
154- );
223+ if (!getFunction ()) return ; // non-function stuff (initializers) are handled separately
224+ Name dceName;
225+ if (getModule ()->getGlobalOrNull (name)) {
226+ // its a global
227+ dceName = parent->globalToDCENode [name];
228+ } else {
229+ // it's an import.
230+ dceName = parent->importIdToDCENode [parent->getImportId (name)];
231+ }
232+ parent->nodes [parent->functionToDCENode [getFunction ()->name ]].reaches .push_back (dceName);
155233 }
156234 };
157235
158236 PassRunner runner (&wasm);
159237 runner.setIsNested (true );
160238 runner.add <Scanner>(this );
161239 runner.run ();
162-
163- // also scan segment offsets
164- Scanner scanner (this );
165- scanner.setModule (&wasm);
166- for (auto & segment : wasm.table .segments ) {
167- scanner.walk (segment.offset );
168- // TODO: currently, all functions in the table are roots, but we
169- // should add an option to refine that
170- for (auto & name : segment.data ) {
171- roots.insert (functionToDCENode[name]);
172- }
173- }
174- for (auto & segment : wasm.memory .segments ) {
175- scanner.walk (segment.offset );
176- }
177240 }
178241
179242private:
@@ -229,6 +292,7 @@ struct MetaDCEGraph {
229292 // Now they are gone, standard optimization passes can do the rest!
230293 PassRunner passRunner (&wasm);
231294 passRunner.add (" remove-unused-module-elements" );
295+ passRunner.add (" reorder-functions" ); // removing functions may alter the optimum order, as # of calls can change
232296 passRunner.run ();
233297 }
234298
@@ -253,13 +317,18 @@ struct MetaDCEGraph {
253317 for (auto root : roots) {
254318 std::cout << " root: " << root.str << ' \n ' ;
255319 }
320+ std::map<Name, ImportId> importMap;
321+ for (auto & pair : importIdToDCENode) {
322+ auto & id = pair.first ;
323+ auto dceName = pair.second ;
324+ importMap[dceName] = id;
325+ }
256326 for (auto & pair : nodes) {
257327 auto name = pair.first ;
258328 auto & node = pair.second ;
259329 std::cout << " node: " << name.str << ' \n ' ;
260- if (DCENodeToImport.find (name) != DCENodeToImport.end ()) {
261- auto * imp = wasm.getImport (DCENodeToImport[name]);
262- std::cout << " is import " << DCENodeToImport[name] << " , " << imp->module .str << ' .' << imp->base .str << ' \n ' ;
330+ if (importMap.find (name) != importMap.end ()) {
331+ std::cout << " is import " << importMap[name] << ' \n ' ;
263332 }
264333 if (DCENodeToExport.find (name) != DCENodeToExport.end ()) {
265334 std::cout << " is export " << DCENodeToExport[name].str << " , " << wasm.getExport (DCENodeToExport[name])->value << ' \n ' ;
@@ -288,6 +357,7 @@ int main(int argc, const char* argv[]) {
288357 bool emitBinary = true ;
289358 bool debugInfo = false ;
290359 std::string graphFile;
360+ bool dump = false ;
291361
292362 Options options (" wasm-metadce" , " This tool performs dead code elimination (DCE) on a larger space "
293363 " that the wasm module is just a part of. For example, if you have "
@@ -350,6 +420,9 @@ int main(int argc, const char* argv[]) {
350420 [&](Options* o, const std::string& argument) {
351421 graphFile = argument;
352422 })
423+ .add (" --dump" , " -d" , " Dump the combined graph file (useful for debugging)" ,
424+ Options::Arguments::Zero,
425+ [&](Options *o, const std::string &arguments) { dump = true ; })
353426 .add_positional (" INFILE" , Options::Arguments::One,
354427 [](Options* o, const std::string& argument) {
355428 o->extra [" infile" ] = argument;
@@ -439,9 +512,8 @@ int main(int argc, const char* argv[]) {
439512 if (!imp->isArray () || imp->size () != 2 || !imp[0 ]->isString () || !imp[1 ]->isString ()) {
440513 Fatal () << " node.import, if it exists, must be an array of two strings. see --help for the form" ;
441514 }
442- auto importName = ImportUtils::getImport (wasm, imp[0 ]->getIString (), imp[1 ]->getIString ())->name ;
443- graph.importToDCENode [importName] = node.name ;
444- graph.DCENodeToImport [node.name ] = importName;
515+ auto id = graph.getImportId (imp[0 ]->getIString (), imp[1 ]->getIString ());
516+ graph.importIdToDCENode [id] = node.name ;
445517 }
446518 // TODO: optimize this copy with a clever move
447519 graph.nodes [node.name ] = node;
@@ -450,6 +522,11 @@ int main(int argc, const char* argv[]) {
450522 // The external graph is now populated. Scan the module
451523 graph.scanWebAssembly ();
452524
525+ // Debug dump the graph, if requested
526+ if (dump) {
527+ graph.dump ();
528+ }
529+
453530 // Perform the DCE
454531 graph.deadCodeElimination ();
455532
0 commit comments