Skip to content

Commit c7fdc01

Browse files
authored
Optimize callers when DAE removes uninhabitable results (#7233)
In principle the optimizer should be using the fact that calls (or any other expressions) that produce uninhabitable types will never return. Previously, when DAE removed unused, uninhabitable results, the caller would lose this useful information and have no way of determining that the call would never return. Fix this by inserting an `unreachable` after the call in the caller. Also run follow-up optimizations on the caller because the new `unreachable` is very likely to lead to improvements.
1 parent 9795fc1 commit c7fdc01

File tree

2 files changed

+80
-7
lines changed

2 files changed

+80
-7
lines changed

src/passes/DeadArgumentElimination.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,12 @@ struct DAE : public Pass {
388388
if (!allDropped) {
389389
continue;
390390
}
391-
removeReturnValue(func.get(), calls, module);
391+
if (removeReturnValue(func.get(), calls, module)) {
392+
// We should optimize the callers.
393+
for (auto* call : calls) {
394+
worthOptimizing.insert(module->getFunction(expressionFuncs[call]));
395+
}
396+
}
392397
// TODO Removing a drop may also open optimization opportunities in the
393398
// callers.
394399
worthOptimizing.insert(func.get());
@@ -413,8 +418,17 @@ struct DAE : public Pass {
413418
private:
414419
std::unordered_map<Call*, Expression**> allDroppedCalls;
415420

416-
void
421+
// Returns `true` if the caller should be optimized.
422+
bool
417423
removeReturnValue(Function* func, std::vector<Call*>& calls, Module* module) {
424+
// If the result type is uninhabitable, then the caller knows the call will
425+
// never return. That useful information would be lost if we did nothing
426+
// else when removing the return value, but we will insert an `unreachable`
427+
// after the call in the caller to preserve the optimization effect. TODO:
428+
// Do this for more complicated uninhabitable types such as non-nullable
429+
// references to structs with non-nullable reference cycles.
430+
bool wasReturnUninhabitable =
431+
func->getResults().isNull() && func->getResults().isNonNullable();
418432
func->setResults(Type::none);
419433
// Remove the drops on the calls. Note that we must do this before updating
420434
// returns in ReturnUpdater, as there may be recursive calls of this
@@ -425,14 +439,22 @@ struct DAE : public Pass {
425439
auto iter = allDroppedCalls.find(call);
426440
assert(iter != allDroppedCalls.end());
427441
Expression** location = iter->second;
428-
*location = call;
442+
if (wasReturnUninhabitable) {
443+
Builder builder(*module);
444+
*location = builder.makeSequence(call, builder.makeUnreachable());
445+
} else {
446+
*location = call;
447+
}
429448
// Update the call's type.
430449
if (call->type != Type::unreachable) {
431450
call->type = Type::none;
432451
}
433452
}
434453
// Remove any return values.
435454
ReturnUtils::removeReturns(func, *module);
455+
// It's definitely worth optimizing the caller after inserting the
456+
// unreachable.
457+
return wasReturnUninhabitable;
436458
}
437459

438460
// Given a function and all the calls to it, see if we can refine the type of

test/lit/passes/dae-optimizing.wast

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
22
;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
33

4-
;; RUN: foreach %s %t wasm-opt --dae-optimizing -S -o - | filecheck %s
4+
;; RUN: foreach %s %t wasm-opt -all --dae-optimizing -S -o - | filecheck %s
55

66
(module
77
(type $0 (func (param f32) (result f32)))
@@ -14,7 +14,7 @@
1414
(type $2 (func (param f64 f32 f32 f64 f32 i32 i32 f64) (result i32)))
1515
;; CHECK: (global $global$0 (mut i32) (i32.const 10))
1616
(global $global$0 (mut i32) (i32.const 10))
17-
;; CHECK: (func $0 (result i32)
17+
;; CHECK: (func $0 (type $3) (result i32)
1818
;; CHECK-NEXT: (local $0 i32)
1919
;; CHECK-NEXT: (local $1 i32)
2020
;; CHECK-NEXT: (drop
@@ -96,13 +96,13 @@
9696
)
9797
(i32.const -11)
9898
)
99-
;; CHECK: (func $1 (result f32)
99+
;; CHECK: (func $1 (type $4) (result f32)
100100
;; CHECK-NEXT: (f32.const 0)
101101
;; CHECK-NEXT: )
102102
(func $1 (; 1 ;) (type $0) (param $0 f32) (result f32)
103103
(f32.const 0)
104104
)
105-
;; CHECK: (func $2 (param $0 f64) (param $1 f32) (param $2 f32) (param $3 f64) (param $4 f32) (param $5 i32) (param $6 i32) (param $7 f64) (result i32)
105+
;; CHECK: (func $2 (type $2) (param $0 f64) (param $1 f32) (param $2 f32) (param $3 f64) (param $4 f32) (param $5 i32) (param $6 i32) (param $7 f64) (result i32)
106106
;; CHECK-NEXT: (call $0)
107107
;; CHECK-NEXT: )
108108
(func $2 (; 2 ;) (type $2) (param $0 f64) (param $1 f32) (param $2 f32) (param $3 f64) (param $4 f32) (param $5 i32) (param $6 i32) (param $7 f64) (result i32)
@@ -118,3 +118,54 @@
118118
)
119119
)
120120

121+
;; Test that we optimize callers when there are unused uninhabitable results.
122+
(module
123+
(func $import (import "" "") (result i32))
124+
(func $impossible (import "" "") (result (ref none)))
125+
;; CHECK: (type $0 (func (result i32)))
126+
127+
;; CHECK: (type $1 (func (result (ref none))))
128+
129+
;; CHECK: (type $2 (func))
130+
131+
;; CHECK: (import "" "" (func $import (type $0) (result i32)))
132+
133+
;; CHECK: (import "" "" (func $impossible (type $1) (result (ref none))))
134+
135+
;; CHECK: (export "export" (func $export))
136+
137+
;; CHECK: (func $export (type $0) (result i32)
138+
;; CHECK-NEXT: (call $internal)
139+
;; CHECK-NEXT: (unreachable)
140+
;; CHECK-NEXT: )
141+
(func $export (export "export") (result i32)
142+
(drop
143+
;; This should be optimized to an unreachable sequence.
144+
(call $internal
145+
(i32.const 0)
146+
)
147+
)
148+
;; Everything else should be removed by DCE.
149+
(drop
150+
(call $internal
151+
(i32.const 1)
152+
)
153+
)
154+
(i32.const 0)
155+
)
156+
;; CHECK: (func $internal (type $2)
157+
;; CHECK-NEXT: (drop
158+
;; CHECK-NEXT: (call $import)
159+
;; CHECK-NEXT: )
160+
;; CHECK-NEXT: (drop
161+
;; CHECK-NEXT: (call $impossible)
162+
;; CHECK-NEXT: )
163+
;; CHECK-NEXT: )
164+
(func $internal (param i32) (result (ref none))
165+
;; Prevent this from being removed entirely.
166+
(drop
167+
(call $import)
168+
)
169+
(call $impossible)
170+
)
171+
)

0 commit comments

Comments
 (0)