Skip to content

Commit 22e1634

Browse files
committed
Merge branch 'wasm-reduce-header' into wasm-reduce-destructive
2 parents eebc710 + a195ebd commit 22e1634

File tree

70 files changed

+3512
-2463
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+3512
-2463
lines changed

scripts/bundle_clusterfuzz.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
'--disable-shared-everything',
109109
'--disable-fp16',
110110
'--disable-strings',
111+
'--disable-stack-switching',
111112
]
112113

113114
with tarfile.open(output_file, "w:gz") as tar:

scripts/clusterfuzz/run.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
'--disable-shared-everything',
9494
'--disable-fp16',
9595
'--disable-strings',
96+
'--disable-stack-switching',
9697
]
9798

9899

scripts/fuzz_opt.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,13 @@ def randomize_feature_opts():
152152
# The shared-everything feature is new and we want to fuzz it, but it
153153
# also currently disables fuzzing V8, so disable it most of the time.
154154
# Same with strings. Relaxed SIMD's nondeterminism disables much but not
155-
# all of our V8 fuzzing, so avoid it too.
155+
# all of our V8 fuzzing, so avoid it too. Stack Switching, as well, is
156+
# not yet ready in V8.
156157
if random.random() < 0.9:
157158
FEATURE_OPTS.append('--disable-shared-everything')
158159
FEATURE_OPTS.append('--disable-strings')
159160
FEATURE_OPTS.append('--disable-relaxed-simd')
161+
FEATURE_OPTS.append('--disable-stack-switching')
160162

161163
print('randomized feature opts:', '\n ' + '\n '.join(FEATURE_OPTS))
162164

@@ -824,8 +826,9 @@ def run(self, wasm, extra_d8_flags=[]):
824826
def can_run(self, wasm):
825827
# V8 does not support shared memories when running with
826828
# shared-everything enabled, so do not fuzz shared-everything
827-
# for now. It also does not yet support strings.
828-
return all_disallowed(['shared-everything', 'strings'])
829+
# for now. It also does not yet support strings, nor stack
830+
# switching
831+
return all_disallowed(['shared-everything', 'strings', 'stack-switching'])
829832

830833
def can_compare_to_self(self):
831834
# With nans, VM differences can confuse us, so only very simple VMs
@@ -1482,11 +1485,17 @@ def handle(self, wasm):
14821485

14831486
# verify that merging in the second module did not alter the output.
14841487
output = run_bynterp(wasm, ['--fuzz-exec-before', '-all'])
1488+
if output == IGNORE:
1489+
return
14851490
output = fix_output(output)
14861491
second_output = run_bynterp(second_wasm, ['--fuzz-exec-before', '-all'])
14871492
second_output = fix_output(second_output)
1493+
if second_output == IGNORE:
1494+
return
14881495
merged_output = run_bynterp(merged, ['--fuzz-exec-before', '-all'])
14891496
merged_output = fix_output(merged_output)
1497+
if merged_output == IGNORE:
1498+
return
14901499

14911500
# If the second module traps in instantiation, then the merged module
14921501
# must do so as well, regardless of what the first module does. (In
@@ -1624,7 +1633,7 @@ def can_run_on_wasm(self, wasm):
16241633
return False
16251634

16261635
# see D8.can_run
1627-
return all_disallowed(['shared-everything', 'strings'])
1636+
return all_disallowed(['shared-everything', 'strings', 'stack-switching'])
16281637

16291638

16301639
# Check that the text format round-trips without error.
@@ -1832,7 +1841,7 @@ def can_run_on_wasm(self, wasm):
18321841
return False
18331842
if NANS:
18341843
return False
1835-
return all_disallowed(['shared-everything', 'strings'])
1844+
return all_disallowed(['shared-everything', 'strings', 'stack-switching'])
18361845

18371846

18381847
# Test --fuzz-preserve-imports-exports, which never modifies imports or exports.

scripts/test/fuzzing.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,6 @@
7070
# the fuzzer does not support imported memories
7171
'multi-memory-lowering-import.wast',
7272
'multi-memory-lowering-import-error.wast',
73-
# the fuzzer does not support typed continuations
74-
'typed_continuations.wast',
75-
'typed_continuations_resume.wast',
76-
'typed_continuations_contnew.wast',
77-
'typed_continuations_contbind.wast',
78-
'typed_continuations_suspend.wast',
7973
# the fuzzer does not support struct RMW ops
8074
'gc-atomics.wast',
8175
'gc-atomics-null-refs.wast',
@@ -96,32 +90,14 @@
9690
# it removes unknown imports
9791
'string-lifting.wast',
9892
'string-lifting-custom-module.wast',
99-
# TODO: fuzzer support for stack switching
100-
'tag_linked.wast',
101-
'stack_switching.wast',
102-
'stack_switching_contnew.wast',
103-
'stack_switching_contbind.wast',
104-
'stack_switching_suspend.wast',
105-
'stack_switching_resume.wast',
106-
'stack_switching_resume_throw.wast',
107-
'stack_switching_switch.wast',
108-
'stack_switching_switch_2.wast',
109-
'O3_stack-switching.wast',
110-
'coalesce-locals-stack-switching.wast',
111-
'dce-stack-switching.wast',
112-
'local-cse-cont.wast',
93+
# TODO: fuzzer support for remaining stack switching instructions: switch,
94+
# cont.bind
95+
'cont.wast',
11396
'precompute-stack-switching.wast',
97+
'coalesce-locals-stack-switching.wast',
98+
'stack_switching_switch_2.wast',
99+
'stack_switching_switch.wast',
114100
'unsubtyping-stack-switching.wast',
115-
'vacuum-stack-switching.wast',
116-
'cont.wast',
117-
'cont_simple.wast',
118-
'gufa-cont.wast',
119-
'cont_many_unhandled.wast',
120-
'cont_export.wast',
121-
'cont_export_throw.wast',
122-
'type-merging-cont.wast',
123-
'remove-unused-module-elements-cont.wast',
124-
'abstract-type-refining-cont.wast',
125101
# TODO: fix split_wast() on tricky escaping situations like a string ending
126102
# in \\" (the " is not escaped - there is an escaped \ before it)
127103
'string-lifting-section.wast',

src/ir/possible-contents.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,12 @@ struct InfoCollector
704704
void visitRefCast(RefCast* curr) { receiveChildValue(curr->ref, curr); }
705705
void visitRefTest(RefTest* curr) { addRoot(curr); }
706706
void visitRefGetDesc(RefGetDesc* curr) {
707-
// TODO: Do something more similar to struct.get here
708-
addRoot(curr);
707+
// Parallel to StructGet.
708+
if (!isRelevant(curr->ref)) {
709+
addRoot(curr);
710+
return;
711+
}
712+
addChildParentLink(curr->ref, curr);
709713
}
710714
void visitBrOn(BrOn* curr) {
711715
// TODO: optimize when possible
@@ -939,6 +943,10 @@ struct InfoCollector
939943
return DataLocation{type, i};
940944
});
941945
}
946+
if (curr->desc) {
947+
info.links.push_back({ExpressionLocation{curr->desc, 0},
948+
DataLocation{type, DataLocation::DescriptorIndex}});
949+
}
942950
addRoot(curr, PossibleContents::exactType(curr->type));
943951
}
944952
void visitArrayNew(ArrayNew* curr) {
@@ -2679,6 +2687,11 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) {
26792687
} else if (auto* set = parent->dynCast<ArraySet>()) {
26802688
assert(set->ref == child || set->value == child);
26812689
writeToData(set->ref, set->value, 0);
2690+
} else if (auto* get = parent->dynCast<RefGetDesc>()) {
2691+
// Similar to struct.get.
2692+
assert(get->ref == child);
2693+
readFromData(
2694+
get->ref->type, DataLocation::DescriptorIndex, contents, get);
26822695
} else {
26832696
// TODO: ref.test and all other casts can be optimized (see the cast
26842697
// helper code used in OptimizeInstructions and RemoveUnusedBrs)
@@ -2839,6 +2852,10 @@ void Flower::filterGlobalContents(PossibleContents& contents,
28392852

28402853
void Flower::filterDataContents(PossibleContents& contents,
28412854
const DataLocation& dataLoc) {
2855+
if (dataLoc.index == DataLocation::DescriptorIndex) {
2856+
// Nothing to filter (packing is not relevant for a descriptor).
2857+
return;
2858+
}
28422859
auto field = GCTypeUtils::getField(dataLoc.type, dataLoc.index);
28432860
if (!field) {
28442861
// This is a bottom type; nothing will be written here.

src/ir/possible-contents.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,9 @@ struct SignatureResultLocation {
481481
struct DataLocation {
482482
HeapType type;
483483
// The index of the field in a struct, or 0 for an array (where we do not
484-
// attempt to differentiate by index).
484+
// attempt to differentiate by index). A special index is used for the
485+
// descriptor field.
486+
static const Index DescriptorIndex = -1;
485487
Index index;
486488
bool operator==(const DataLocation& other) const {
487489
return type == other.type && index == other.index;

src/passes/DeadArgumentElimination.cpp

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ using DAEFunctionInfoMap = std::unordered_map<Name, DAEFunctionInfo>;
9494
struct DAEScanner
9595
: public WalkerPass<PostWalker<DAEScanner, Visitor<DAEScanner>>> {
9696
bool isFunctionParallel() override { return true; }
97+
bool modifiesBinaryenIR() override { return false; }
9798

9899
std::unique_ptr<Pass> create() override {
99100
return std::make_unique<DAEScanner>(infoMap);
@@ -188,6 +189,11 @@ struct DAE : public Pass {
188189

189190
bool optimize = false;
190191

192+
Index numFunctions;
193+
194+
// Map of function names to indexes. This lets us use indexes below for speed.
195+
std::unordered_map<Name, Index> indexes;
196+
191197
void run(Module* module) override {
192198
DAEFunctionInfoMap infoMap;
193199
// Ensure all entries exist so the parallel threads don't modify the data
@@ -198,6 +204,12 @@ struct DAE : public Pass {
198204
// The null name represents module-level code (not in a function).
199205
infoMap[Name()];
200206

207+
numFunctions = module->functions.size();
208+
209+
for (Index i = 0; i < numFunctions; i++) {
210+
indexes[module->functions[i]->name] = i;
211+
}
212+
201213
// Iterate to convergence.
202214
while (1) {
203215
if (!iteration(module, infoMap)) {
@@ -233,34 +245,36 @@ struct DAE : public Pass {
233245
Call* call;
234246
Function* func;
235247
};
236-
std::map<Name, std::vector<Call*>> allCalls;
237-
std::unordered_set<Name> tailCallees;
238-
std::unordered_set<Name> hasUnseenCalls;
248+
249+
std::vector<std::vector<Call*>> allCalls(numFunctions);
250+
std::vector<bool> tailCallees(numFunctions);
251+
std::vector<bool> hasUnseenCalls(numFunctions);
252+
239253
// Track the function in which relevant expressions exist. When we modify
240254
// those expressions we will need to mark the function's info as stale.
241255
std::unordered_map<Expression*, Name> expressionFuncs;
242256
for (auto& [func, info] : infoMap) {
243257
for (auto& [name, calls] : info.calls) {
244-
auto& allCallsToName = allCalls[name];
258+
auto& allCallsToName = allCalls[indexes[name]];
245259
allCallsToName.insert(allCallsToName.end(), calls.begin(), calls.end());
246260
for (auto* call : calls) {
247261
expressionFuncs[call] = func;
248262
}
249263
}
250264
for (auto& callee : info.tailCallees) {
251-
tailCallees.insert(callee);
265+
tailCallees[indexes[callee]] = true;
252266
}
253267
for (auto& [call, dropp] : info.droppedCalls) {
254268
allDroppedCalls[call] = dropp;
255269
}
256270
for (auto& name : info.hasUnseenCalls) {
257-
hasUnseenCalls.insert(name);
271+
hasUnseenCalls[indexes[name]] = true;
258272
}
259273
}
260274
// Exports are considered unseen calls.
261275
for (auto& curr : module->exports) {
262276
if (curr->kind == ExternalKind::Function) {
263-
hasUnseenCalls.insert(*curr->getInternalName());
277+
hasUnseenCalls[indexes[*curr->getInternalName()]] = true;
264278
}
265279
}
266280

@@ -299,23 +313,32 @@ struct DAE : public Pass {
299313

300314
// We now have a mapping of all call sites for each function, and can look
301315
// for optimization opportunities.
302-
for (auto& [name, calls] : allCalls) {
316+
for (Index index = 0; index < numFunctions; index++) {
317+
auto* func = module->functions[index].get();
318+
if (func->imported()) {
319+
continue;
320+
}
303321
// We can only optimize if we see all the calls and can modify them.
304-
if (hasUnseenCalls.count(name)) {
322+
if (hasUnseenCalls[index]) {
323+
continue;
324+
}
325+
auto& calls = allCalls[index];
326+
if (calls.empty()) {
327+
// Nothing calls this, so it is not worth optimizing.
305328
continue;
306329
}
307-
auto* func = module->getFunction(name);
308330
// Refine argument types before doing anything else. This does not
309331
// affect whether an argument is used or not, it just refines the type
310332
// where possible.
333+
auto name = func->name;
311334
if (refineArgumentTypes(func, calls, module, infoMap[name])) {
312335
worthOptimizing.insert(func);
313336
markStale(func->name);
314337
}
315338
// Refine return types as well.
316339
if (refineReturnTypes(func, calls, module)) {
317340
refinedReturnTypes = true;
318-
markStale(func->name);
341+
markStale(name);
319342
markCallersStale(calls);
320343
}
321344
auto optimizedIndexes =
@@ -336,21 +359,29 @@ struct DAE : public Pass {
336359
ReFinalize().run(getPassRunner(), module);
337360
}
338361
// We now know which parameters are unused, and can potentially remove them.
339-
for (auto& [name, calls] : allCalls) {
340-
if (hasUnseenCalls.count(name)) {
362+
for (Index index = 0; index < numFunctions; index++) {
363+
auto* func = module->functions[index].get();
364+
if (func->imported()) {
365+
continue;
366+
}
367+
if (hasUnseenCalls[index]) {
341368
continue;
342369
}
343-
auto* func = module->getFunction(name);
344370
auto numParams = func->getNumParams();
345371
if (numParams == 0) {
346372
continue;
347373
}
374+
auto& calls = allCalls[index];
375+
if (calls.empty()) {
376+
continue;
377+
}
378+
auto name = func->name;
348379
auto [removedIndexes, outcome] = ParamUtils::removeParameters(
349380
{func}, infoMap[name].unusedParams, calls, {}, module, getPassRunner());
350381
if (!removedIndexes.empty()) {
351382
// Success!
352383
worthOptimizing.insert(func);
353-
markStale(func->name);
384+
markStale(name);
354385
markCallersStale(calls);
355386
}
356387
if (outcome == ParamUtils::RemovalOutcome::Failure) {
@@ -362,25 +393,28 @@ struct DAE : public Pass {
362393
// modified allCalls (we can't modify a call site twice in one iteration,
363394
// once to remove a param, once to drop the return value).
364395
if (worthOptimizing.empty()) {
365-
for (auto& func : module->functions) {
396+
for (Index index = 0; index < numFunctions; index++) {
397+
auto& func = module->functions[index];
398+
if (func->imported()) {
399+
continue;
400+
}
366401
if (func->getResults() == Type::none) {
367402
continue;
368403
}
369-
auto name = func->name;
370-
if (hasUnseenCalls.count(name)) {
404+
if (hasUnseenCalls[index]) {
371405
continue;
372406
}
407+
auto name = func->name;
373408
if (infoMap[name].hasTailCalls) {
374409
continue;
375410
}
376-
if (tailCallees.count(name)) {
411+
if (tailCallees[index]) {
377412
continue;
378413
}
379-
auto iter = allCalls.find(name);
380-
if (iter == allCalls.end()) {
414+
auto& calls = allCalls[index];
415+
if (calls.empty()) {
381416
continue;
382417
}
383-
auto& calls = iter->second;
384418
bool allDropped =
385419
std::all_of(calls.begin(), calls.end(), [&](Call* call) {
386420
return allDroppedCalls.count(call);
@@ -397,7 +431,7 @@ struct DAE : public Pass {
397431
// TODO Removing a drop may also open optimization opportunities in the
398432
// callers.
399433
worthOptimizing.insert(func.get());
400-
markStale(func->name);
434+
markStale(name);
401435
markCallersStale(calls);
402436
}
403437
}

0 commit comments

Comments
 (0)