summaryrefslogtreecommitdiff
path: root/src/passes/RemoveUnusedModuleElements.cpp
blob: 0cafd9d9d1fbada143b4eda04f00eba48b6902ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*
 * Copyright 2016 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//
// Removes module elements that are are never used: functions, globals, and
// tags, which may be imported or not, and function types (which we merge and
// remove if unneeded)
//

#include <memory>

#include "ir/element-utils.h"
#include "ir/intrinsics.h"
#include "ir/module-utils.h"
#include "ir/utils.h"
#include "pass.h"
#include "wasm-builder.h"
#include "wasm.h"

namespace wasm {

enum class ModuleElementKind { Function, Global, Tag, Table, ElementSegment };

typedef std::pair<ModuleElementKind, Name> ModuleElement;

// Finds reachabilities
// TODO: use Effects to determine if a memory is used
// This pass does not have multi-memories support

struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
  Module* module;
  std::vector<ModuleElement> queue;
  std::set<ModuleElement> reachable;
  bool usesMemory = false;

  // The signatures that we have seen a call_ref for. When we see a RefFunc of a
  // signature in here, we know it is reachable.
  std::unordered_set<HeapType> calledSignatures;

  // All the RefFuncs we've seen, grouped by heap type. When we see a CallRef of
  // one of the types here, we know all the RefFuncs corresponding to it are
  // reachable. This is the reverse side of calledSignatures: for a function to
  // be reached via a reference, we need the combination of a RefFunc of it as
  // well as a CallRef of that, and we may see them in any order. (Or, if the
  // RefFunc is in a table, we need a CallIndirect, which is handled in the
  // table logic.)
  //
  // After we see a call for a type, we can clear out the entry here for it, as
  // we'll have that type in calledSignatures, and so this contains only
  // RefFuncs that we have not seen a call for yet, hence "uncalledRefFuncMap."
  //
  // TODO: We assume a closed world in the GC space atm, but eventually should
  //       have a flag for that, and when the world is not closed we'd need to
  //       check for RefFuncs that flow out to exports or imports
  std::unordered_map<HeapType, std::unordered_set<Name>> uncalledRefFuncMap;

  ReachabilityAnalyzer(Module* module, const std::vector<ModuleElement>& roots)
    : module(module) {
    queue = roots;
    // Globals used in memory/table init expressions are also roots
    for (auto& segment : module->dataSegments) {
      if (!segment->isPassive) {
        walk(segment->offset);
      }
    }
    for (auto& segment : module->elementSegments) {
      if (segment->table.is()) {
        walk(segment->offset);
      }
    }

    // main loop
    while (queue.size()) {
      auto curr = queue.back();
      queue.pop_back();
      if (reachable.emplace(curr).second) {
        auto& [kind, value] = curr;
        if (kind == ModuleElementKind::Function) {
          // if not an import, walk it
          auto* func = module->getFunction(value);
          if (!func->imported()) {
            walk(func->body);
          }
        } else if (kind == ModuleElementKind::Global) {
          // if not imported, it has an init expression we need to walk
          auto* global = module->getGlobal(value);
          if (!global->imported()) {
            walk(global->init);
          }
        } else if (kind == ModuleElementKind::Table) {
          ModuleUtils::iterTableSegments(
            *module, curr.second, [&](ElementSegment* segment) {
              walk(segment->offset);
            });
        }
      }
    }
  }

  void maybeAdd(ModuleElement element) {
    if (reachable.count(element) == 0) {
      queue.emplace_back(element);
    }
  }

  // Add a reference to a table and all its segments and elements.
  void maybeAddTable(Name name) {
    maybeAdd(ModuleElement(ModuleElementKind::Table, name));
    ModuleUtils::iterTableSegments(*module, name, [&](ElementSegment* segment) {
      maybeAdd(ModuleElement(ModuleElementKind::ElementSegment, segment->name));
    });
  }

  void visitCall(Call* curr) {
    maybeAdd(ModuleElement(ModuleElementKind::Function, curr->target));

    if (Intrinsics(*module).isCallWithoutEffects(curr)) {
      // A call-without-effects receives a function reference and calls it, the
      // same as a CallRef. When we have a flag for non-closed-world, we should
      // handle this automatically by the reference flowing out to an import,
      // which is what binaryen intrinsics look like. For now, to support use
      // cases of a closed world but that also use this intrinsic, handle the
      // intrinsic specifically here. (Without that, the closed world assumption
      // makes us ignore the function ref that flows to an import, so we are not
      // aware that it is actually called.)
      auto* target = curr->operands.back();
      if (auto* refFunc = target->dynCast<RefFunc>()) {
        // We can see exactly where this goes.
        Call call(module->allocator);
        call.target = refFunc->func;
        visitCall(&call);
      } else {
        // All we can see is the type, so do a CallRef of that.
        CallRef callRef(module->allocator);
        callRef.target = target;
        visitCallRef(&callRef);
      }
    }
  }

  void visitCallIndirect(CallIndirect* curr) { maybeAddTable(curr->table); }

  void visitCallRef(CallRef* curr) {
    // Ignore unreachable code.
    if (!curr->target->type.isRef()) {
      return;
    }

    auto type = curr->target->type.getHeapType();

    // Call all the functions of that signature. We can then forget about
    // them, as this signature will be marked as called.
    auto iter = uncalledRefFuncMap.find(type);
    if (iter != uncalledRefFuncMap.end()) {
      // We must not have a type in both calledSignatures and
      // uncalledRefFuncMap: once it is called, we do not track RefFuncs for
      // it any more.
      assert(calledSignatures.count(type) == 0);

      for (Name target : iter->second) {
        maybeAdd(ModuleElement(ModuleElementKind::Function, target));
      }

      uncalledRefFuncMap.erase(iter);
    }

    calledSignatures.insert(type);
  }

  void visitGlobalGet(GlobalGet* curr) {
    maybeAdd(ModuleElement(ModuleElementKind::Global, curr->name));
  }
  void visitGlobalSet(GlobalSet* curr) {
    maybeAdd(ModuleElement(ModuleElementKind::Global, curr->name));
  }

  void visitLoad(Load* curr) { usesMemory = true; }
  void visitStore(Store* curr) { usesMemory = true; }
  void visitAtomicCmpxchg(AtomicCmpxchg* curr) { usesMemory = true; }
  void visitAtomicRMW(AtomicRMW* curr) { usesMemory = true; }
  void visitAtomicWait(AtomicWait* curr) { usesMemory = true; }
  void visitAtomicNotify(AtomicNotify* curr) { usesMemory = true; }
  void visitAtomicFence(AtomicFence* curr) { usesMemory = true; }
  void visitMemoryInit(MemoryInit* curr) { usesMemory = true; }
  void visitDataDrop(DataDrop* curr) { usesMemory = true; }
  void visitMemoryCopy(MemoryCopy* curr) { usesMemory = true; }
  void visitMemoryFill(MemoryFill* curr) { usesMemory = true; }
  void visitMemorySize(MemorySize* curr) { usesMemory = true; }
  void visitMemoryGrow(MemoryGrow* curr) { usesMemory = true; }
  void visitRefFunc(RefFunc* curr) {
    auto type = curr->type.getHeapType();
    if (calledSignatures.count(type)) {
      // We must not have a type in both calledSignatures and
      // uncalledRefFuncMap: once it is called, we do not track RefFuncs for it
      // any more.
      assert(uncalledRefFuncMap.count(type) == 0);

      // We've seen a RefFunc for this, so it is reachable.
      maybeAdd(ModuleElement(ModuleElementKind::Function, curr->func));
    } else {
      // We've never seen a CallRef for this, but might see one later.
      uncalledRefFuncMap[type].insert(curr->func);
    }
  }
  void visitTableGet(TableGet* curr) { maybeAddTable(curr->table); }
  void visitTableSet(TableSet* curr) { maybeAddTable(curr->table); }
  void visitTableSize(TableSize* curr) { maybeAddTable(curr->table); }
  void visitTableGrow(TableGrow* curr) { maybeAddTable(curr->table); }
  void visitThrow(Throw* curr) {
    maybeAdd(ModuleElement(ModuleElementKind::Tag, curr->tag));
  }
  void visitTry(Try* curr) {
    for (auto tag : curr->catchTags) {
      maybeAdd(ModuleElement(ModuleElementKind::Tag, tag));
    }
  }
};

struct RemoveUnusedModuleElements : public Pass {
  // This pass only removes module elements, it never modifies function
  // contents.
  bool requiresNonNullableLocalFixups() override { return false; }

  bool rootAllFunctions;

  RemoveUnusedModuleElements(bool rootAllFunctions)
    : rootAllFunctions(rootAllFunctions) {}

  void run(Module* module) override {
    std::vector<ModuleElement> roots;
    // Module start is a root.
    if (module->start.is()) {
      auto startFunction = module->getFunction(module->start);
      // Can be skipped if the start function is empty.
      if (!startFunction->imported() && startFunction->body->is<Nop>()) {
        module->start = Name{};
      } else {
        roots.emplace_back(ModuleElementKind::Function, module->start);
      }
    }
    // If told to, root all the functions
    if (rootAllFunctions) {
      ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) {
        roots.emplace_back(ModuleElementKind::Function, func->name);
      });
    }
    ModuleUtils::iterActiveElementSegments(
      *module, [&](ElementSegment* segment) {
        auto table = module->getTable(segment->table);
        if (table->imported() && !segment->data.empty()) {
          roots.emplace_back(ModuleElementKind::ElementSegment, segment->name);
        }
      });
    // Exports are roots.
    bool exportsMemory = false;
    for (auto& curr : module->exports) {
      if (curr->kind == ExternalKind::Function) {
        roots.emplace_back(ModuleElementKind::Function, curr->value);
      } else if (curr->kind == ExternalKind::Global) {
        roots.emplace_back(ModuleElementKind::Global, curr->value);
      } else if (curr->kind == ExternalKind::Tag) {
        roots.emplace_back(ModuleElementKind::Tag, curr->value);
      } else if (curr->kind == ExternalKind::Table) {
        roots.emplace_back(ModuleElementKind::Table, curr->value);
        ModuleUtils::iterTableSegments(
          *module, curr->value, [&](ElementSegment* segment) {
            roots.emplace_back(ModuleElementKind::ElementSegment,
                               segment->name);
          });
      } else if (curr->kind == ExternalKind::Memory) {
        exportsMemory = true;
      }
    }
    // Check for special imports, which are roots.
    bool importsMemory = false;
    if (!module->memories.empty() && module->memories[0]->imported()) {
      importsMemory = true;
    }
    // For now, all functions that can be called indirectly are marked as roots.
    // TODO: Compute this based on which ElementSegments are actually reachable,
    //       and which functions have a call_indirect of the proper type.
    ElementUtils::iterAllElementFunctionNames(module, [&](Name& name) {
      roots.emplace_back(ModuleElementKind::Function, name);
    });
    // Compute reachability starting from the root set.
    ReachabilityAnalyzer analyzer(module, roots);

    // RefFuncs that are never called are a special case: We cannot remove the
    // function, since then (ref.func $foo) would not validate. But if we know
    // it is never called, at least the contents do not matter, so we can
    // empty it out.
    std::unordered_set<Name> uncalledRefFuncs;
    for (auto& [type, targets] : analyzer.uncalledRefFuncMap) {
      for (auto target : targets) {
        uncalledRefFuncs.insert(target);
      }

      // We cannot have a type in both this map and calledSignatures.
      assert(analyzer.calledSignatures.count(type) == 0);
    }

#ifndef NDEBUG
    for (auto type : analyzer.calledSignatures) {
      assert(analyzer.uncalledRefFuncMap.count(type) == 0);
    }
#endif

    // Remove unreachable elements.
    module->removeFunctions([&](Function* curr) {
      if (analyzer.reachable.count(
            ModuleElement(ModuleElementKind::Function, curr->name))) {
        // This is reached.
        return false;
      }

      if (uncalledRefFuncs.count(curr->name)) {
        // This is not reached, but has a reference. See comment above on
        // uncalledRefFuncs.
        if (!curr->imported()) {
          curr->body = Builder(*module).makeUnreachable();
        }
        return false;
      }

      // The function is not reached and has no reference; remove it.
      return true;
    });
    module->removeGlobals([&](Global* curr) {
      return analyzer.reachable.count(
               ModuleElement(ModuleElementKind::Global, curr->name)) == 0;
    });
    module->removeTags([&](Tag* curr) {
      return analyzer.reachable.count(
               ModuleElement(ModuleElementKind::Tag, curr->name)) == 0;
    });
    module->removeElementSegments([&](ElementSegment* curr) {
      return curr->data.empty() ||
             analyzer.reachable.count(ModuleElement(
               ModuleElementKind::ElementSegment, curr->name)) == 0;
    });
    // Since we've removed all empty element segments, here we mark all tables
    // that have a segment left.
    std::unordered_set<Name> nonemptyTables;
    ModuleUtils::iterActiveElementSegments(
      *module,
      [&](ElementSegment* segment) { nonemptyTables.insert(segment->table); });
    module->removeTables([&](Table* curr) {
      return (nonemptyTables.count(curr->name) == 0 || !curr->imported()) &&
             analyzer.reachable.count(
               ModuleElement(ModuleElementKind::Table, curr->name)) == 0;
    });
    // TODO: After removing elements, we may be able to remove more things, and
    //       should continue to work. (For example, after removing a reference
    //       to a function from an element segment, we may be able to remove
    //       that function, etc.)

    // Handle the memory
    if (!exportsMemory && !analyzer.usesMemory) {
      if (!importsMemory) {
        // The memory is unobservable to the outside, we can remove the
        // contents.
        module->removeDataSegments([&](DataSegment* curr) { return true; });
      }
      if (module->dataSegments.empty() && !module->memories.empty()) {
        module->removeMemory(module->memories[0]->name);
      }
    }
  }
};

Pass* createRemoveUnusedModuleElementsPass() {
  return new RemoveUnusedModuleElements(false);
}

Pass* createRemoveUnusedNonFunctionModuleElementsPass() {
  return new RemoveUnusedModuleElements(true);
}

} // namespace wasm