Skip to content

Commit 5f88dcd

Browse files
authored
[Inlining] Preserve return_calls when possible (#4589)
We can preserve return_calls in inlined functions when the inlined call site is itself a return_call, since the call result types must transitively match in that case. This solves a problem where the previous inlining logic could introduce stack exhaustion by downgrading recursive return_calls to normal calls. Fixes #4587.
1 parent 5d5e465 commit 5f88dcd

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

src/passes/Inlining.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ struct Updater : public PostWalker<Updater> {
247247
Module* module;
248248
std::map<Index, Index> localMapping;
249249
Name returnName;
250+
bool isReturn;
250251
Builder* builder;
251252
void visitReturn(Return* curr) {
252253
replaceCurrent(builder->makeBreak(returnName, curr->value));
@@ -257,6 +258,14 @@ struct Updater : public PostWalker<Updater> {
257258
// not cause unbounded stack growth because inlining and return calling both
258259
// avoid creating a new stack frame.
259260
template<typename T> void handleReturnCall(T* curr, HeapType targetType) {
261+
if (isReturn) {
262+
// If the inlined callsite was already a return_call, then we can keep
263+
// return_calls in the inlined function rather than downgrading them.
264+
// That is, if A->B and B->C and both those calls are return_calls
265+
// then after inlining A->B we want to now have A->C be a
266+
// return_call.
267+
return;
268+
}
260269
curr->isReturn = false;
261270
curr->type = targetType.getSignature().results;
262271
if (curr->type.isConcrete()) {
@@ -329,6 +338,7 @@ doInlining(Module* module, Function* into, const InliningAction& action) {
329338
Updater updater;
330339
updater.module = module;
331340
updater.returnName = block->name;
341+
updater.isReturn = call->isReturn;
332342
updater.builder = &builder;
333343
// Set up a locals mapping
334344
for (Index i = 0; i < from->getNumLocals(); i++) {

test/lit/passes/inlining_enable-tail-call.wast

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,3 +705,65 @@
705705
(unreachable)
706706
)
707707
)
708+
709+
(module
710+
;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32)))
711+
712+
;; CHECK: (export "is_even" (func $is_even))
713+
(export "is_even" (func $is_even))
714+
;; CHECK: (func $is_even (param $i i32) (result i32)
715+
;; CHECK-NEXT: (local $1 i32)
716+
;; CHECK-NEXT: (if (result i32)
717+
;; CHECK-NEXT: (i32.eqz
718+
;; CHECK-NEXT: (local.get $i)
719+
;; CHECK-NEXT: )
720+
;; CHECK-NEXT: (i32.const 1)
721+
;; CHECK-NEXT: (return
722+
;; CHECK-NEXT: (block $__inlined_func$is_odd (result i32)
723+
;; CHECK-NEXT: (local.set $1
724+
;; CHECK-NEXT: (i32.sub
725+
;; CHECK-NEXT: (local.get $i)
726+
;; CHECK-NEXT: (i32.const 1)
727+
;; CHECK-NEXT: )
728+
;; CHECK-NEXT: )
729+
;; CHECK-NEXT: (if (result i32)
730+
;; CHECK-NEXT: (i32.eqz
731+
;; CHECK-NEXT: (local.get $1)
732+
;; CHECK-NEXT: )
733+
;; CHECK-NEXT: (i32.const 0)
734+
;; CHECK-NEXT: (return_call $is_even
735+
;; CHECK-NEXT: (i32.sub
736+
;; CHECK-NEXT: (local.get $1)
737+
;; CHECK-NEXT: (i32.const 1)
738+
;; CHECK-NEXT: )
739+
;; CHECK-NEXT: )
740+
;; CHECK-NEXT: )
741+
;; CHECK-NEXT: )
742+
;; CHECK-NEXT: )
743+
;; CHECK-NEXT: )
744+
;; CHECK-NEXT: )
745+
(func $is_even (param $i i32) (result i32)
746+
(if (result i32)
747+
(i32.eqz (local.get $i))
748+
(i32.const 1)
749+
(return_call $is_odd
750+
(i32.sub
751+
(local.get $i)
752+
(i32.const 1)
753+
)
754+
)
755+
)
756+
)
757+
(func $is_odd (param $i i32) (result i32)
758+
(if (result i32)
759+
(i32.eqz (local.get $i))
760+
(i32.const 0)
761+
(return_call $is_even
762+
(i32.sub
763+
(local.get $i)
764+
(i32.const 1)
765+
)
766+
)
767+
)
768+
)
769+
)

0 commit comments

Comments
 (0)