Skip to content

Commit 4201437

Browse files
authored
Revert "DeadArgumentElimination: Refine result types of exported functions" (#7943)
Reverts #7935 Refining exports is unsafe given exact casts: the importing module can notice that we refined, as exact casts to the old type will fail. Doing this only when custom descriptors (exact casts) are disabled is possible, but rarely useful, and it's better to focus on optimizations that work with newer features.
1 parent 7ba340a commit 4201437

File tree

3 files changed

+46
-274
lines changed

3 files changed

+46
-274
lines changed

src/passes/DeadArgumentElimination.cpp

Lines changed: 45 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
#include "passes/opt-utils.h"
5151
#include "support/sorted_vector.h"
5252
#include "wasm-builder.h"
53-
#include "wasm-type.h"
5453
#include "wasm.h"
5554

5655
namespace wasm {
@@ -77,9 +76,12 @@ struct DAEFunctionInfo {
7776
// removed as well.
7877
bool hasTailCalls = false;
7978
std::unordered_set<Name> tailCallees;
80-
// The set of functions that have their reference taken (which means there may
81-
// be non-direct calls, limiting what we can do).
82-
std::unordered_set<Name> hasRef;
79+
// The set of functions that have calls from places that limit what we can do.
80+
// For now, any call we don't see inhibits our optimizations, but TODO: an
81+
// export could be worked around by exporting a thunk that adds the parameter.
82+
//
83+
// This is built up in parallel in each function, and combined at the end.
84+
std::unordered_set<Name> hasUnseenCalls;
8385

8486
// Clears all data, which marks us as stale and in need of recomputation.
8587
void clear() { *this = DAEFunctionInfo(); }
@@ -141,9 +143,12 @@ struct DAEScanner
141143
// the infoMap).
142144
auto* currInfo = info ? info : &(*infoMap)[Name()];
143145

146+
// Treat a ref.func as an unseen call, preventing us from changing the
147+
// function's type. If we did change it, it could be an observable
148+
// difference from the outside, if the reference escapes, for example.
144149
// TODO: look for actual escaping?
145150
// TODO: create a thunk for external uses that allow internal optimizations
146-
currInfo->hasRef.insert(curr->func);
151+
currInfo->hasUnseenCalls.insert(curr->func);
147152
}
148153

149154
// main entry point
@@ -243,7 +248,7 @@ struct DAE : public Pass {
243248

244249
std::vector<std::vector<Call*>> allCalls(numFunctions);
245250
std::vector<bool> tailCallees(numFunctions);
246-
std::vector<bool> hasRef(numFunctions);
251+
std::vector<bool> hasUnseenCalls(numFunctions);
247252

248253
// Track the function in which relevant expressions exist. When we modify
249254
// those expressions we will need to mark the function's info as stale.
@@ -262,17 +267,14 @@ struct DAE : public Pass {
262267
for (auto& [call, dropp] : info.droppedCalls) {
263268
allDroppedCalls[call] = dropp;
264269
}
265-
for (auto& name : info.hasRef) {
266-
hasRef[indexes[name]] = true;
270+
for (auto& name : info.hasUnseenCalls) {
271+
hasUnseenCalls[indexes[name]] = true;
267272
}
268273
}
269-
270-
// Exports limit some optimizations, for example, we cannot remove or refine
271-
// parameters. TODO: we could export a thunk that drops the parameter etc.
272-
std::vector<bool> isExported(numFunctions);
274+
// Exports are considered unseen calls.
273275
for (auto& curr : module->exports) {
274276
if (curr->kind == ExternalKind::Function) {
275-
isExported[indexes[*curr->getInternalName()]] = true;
277+
hasUnseenCalls[indexes[*curr->getInternalName()]] = true;
276278
}
277279
}
278280

@@ -316,48 +318,38 @@ struct DAE : public Pass {
316318
if (func->imported()) {
317319
continue;
318320
}
319-
// References prevent optimization.
320-
if (hasRef[index]) {
321+
// We can only optimize if we see all the calls and can modify them.
322+
if (hasUnseenCalls[index]) {
321323
continue;
322324
}
323325
auto& calls = allCalls[index];
324-
if (calls.empty() && !isExported[index]) {
326+
if (calls.empty()) {
325327
// Nothing calls this, so it is not worth optimizing.
326328
continue;
327329
}
330+
// Refine argument types before doing anything else. This does not
331+
// affect whether an argument is used or not, it just refines the type
332+
// where possible.
328333
auto name = func->name;
329-
// Exports prevent refining of argument types, as we don't see all the
330-
// calls.
331-
if (!isExported[index]) {
332-
// Refine argument types before doing anything else. This does not
333-
// affect whether an argument is used or not, it just refines the type
334-
// where possible.
335-
if (refineArgumentTypes(func, calls, module, infoMap[name])) {
336-
worthOptimizing.insert(func);
337-
markStale(func->name);
338-
}
334+
if (refineArgumentTypes(func, calls, module, infoMap[name])) {
335+
worthOptimizing.insert(func);
336+
markStale(func->name);
339337
}
340-
// Refine return types as well. Note that exports do *not* prevent this!
341-
// It is valid to export a function that returns something even more
342-
// refined.
343-
if (refineReturnTypes(func, calls, module, isExported[index])) {
338+
// Refine return types as well.
339+
if (refineReturnTypes(func, calls, module)) {
344340
refinedReturnTypes = true;
345341
markStale(name);
346342
markCallersStale(calls);
347343
}
348-
// Exports prevent applying of constant values, as we don't see all the
349-
// calls.
350-
if (!isExported[index]) {
351-
auto optimizedIndexes =
352-
ParamUtils::applyConstantValues({func}, calls, {}, module);
353-
for (auto i : optimizedIndexes) {
354-
// Mark it as unused, which we know it now is (no point to re-scan
355-
// just for that).
356-
infoMap[name].unusedParams.insert(i);
357-
}
358-
if (!optimizedIndexes.empty()) {
359-
markStale(func->name);
360-
}
344+
auto optimizedIndexes =
345+
ParamUtils::applyConstantValues({func}, calls, {}, module);
346+
for (auto i : optimizedIndexes) {
347+
// Mark it as unused, which we know it now is (no point to re-scan just
348+
// for that).
349+
infoMap[name].unusedParams.insert(i);
350+
}
351+
if (!optimizedIndexes.empty()) {
352+
markStale(func->name);
361353
}
362354
}
363355
if (refinedReturnTypes) {
@@ -372,7 +364,7 @@ struct DAE : public Pass {
372364
if (func->imported()) {
373365
continue;
374366
}
375-
if (hasRef[index] || isExported[index]) {
367+
if (hasUnseenCalls[index]) {
376368
continue;
377369
}
378370
auto numParams = func->getNumParams();
@@ -409,7 +401,7 @@ struct DAE : public Pass {
409401
if (func->getResults() == Type::none) {
410402
continue;
411403
}
412-
if (hasRef[index] || isExported[index]) {
404+
if (hasUnseenCalls[index]) {
413405
continue;
414406
}
415407
auto name = func->name;
@@ -578,59 +570,22 @@ struct DAE : public Pass {
578570
// the middle, etc.
579571
bool refineReturnTypes(Function* func,
580572
const std::vector<Call*>& calls,
581-
Module* module,
582-
bool isExported) {
583-
if (isExported && !func->type.isOpen()) {
584-
// We must subtype the current type, so that imports of it work, but it is
585-
// closed.
586-
return false;
587-
}
588-
573+
Module* module) {
589574
auto lub = LUB::getResultsLUB(func, *module);
590575
if (!lub.noted()) {
591576
return false;
592577
}
593578
auto newType = lub.getLUB();
594-
if (newType == func->getResults()) {
595-
return false;
596-
}
597-
598-
// If this is exported, we cannot refine to an exact type without the
599-
// custom descriptors feature being enabled.
600-
if (isExported && !module->features.hasCustomDescriptors()) {
601-
// Remove exactness.
602-
std::vector<Type> inexact;
603-
for (auto t : newType) {
604-
inexact.push_back(t.isRef() ? t.with(Inexact) : t);
605-
}
606-
newType = Type(inexact);
607-
if (newType == func->getResults()) {
608-
return false;
609-
}
610-
}
611-
612-
if (!isExported) {
579+
if (newType != func->getResults()) {
613580
func->setResults(newType);
614-
} else {
615-
// We must explicitly subtype the old type.
616-
TypeBuilder builder(1);
617-
builder[0] = Signature(func->getParams(), newType);
618-
builder[0].subTypeOf(func->type);
619-
// Make this subtype open like the super. This is not necessary, but might
620-
// allow more work later after other changes, in theory.
621-
builder[0].setOpen();
622-
auto result = builder.build();
623-
assert(!result.getError());
624-
func->type = (*result)[0];
625-
}
626-
627-
for (auto* call : calls) {
628-
if (call->type != Type::unreachable) {
629-
call->type = newType;
581+
for (auto* call : calls) {
582+
if (call->type != Type::unreachable) {
583+
call->type = newType;
584+
}
630585
}
586+
return true;
631587
}
632-
633-
return true;
588+
return false;
634589
}
635590
};
636591

test/lit/passes/dae-gc-no-cd.wast

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)