@@ -146,7 +146,17 @@ struct Planner : public WalkerPass<PostWalker<Planner>> {
146146 // plan to inline if we know this is valid to inline, and if the call is
147147 // actually performed - if it is dead code, it's pointless to inline.
148148 // we also cannot inline ourselves.
149- if (state->worthInlining .count (curr->target ) && curr->type != unreachable &&
149+ bool isUnreachable;
150+ if (curr->isReturn ) {
151+ // Tail calls are only actually unreachable if an argument is
152+ isUnreachable =
153+ std::any_of (curr->operands .begin (),
154+ curr->operands .end (),
155+ [](Expression* op) { return op->type == unreachable; });
156+ } else {
157+ isUnreachable = curr->type == unreachable;
158+ }
159+ if (state->worthInlining .count (curr->target ) && !isUnreachable &&
150160 curr->target != getFunction ()->name ) {
151161 // nest the call in a block. that way the location of the pointer to the
152162 // call will not change even if we inline multiple times into the same
@@ -164,32 +174,69 @@ struct Planner : public WalkerPass<PostWalker<Planner>> {
164174 InliningState* state;
165175};
166176
177+ struct Updater : public PostWalker <Updater> {
178+ Module* module ;
179+ std::map<Index, Index> localMapping;
180+ Name returnName;
181+ Builder* builder;
182+ void visitReturn (Return* curr) {
183+ replaceCurrent (builder->makeBreak (returnName, curr->value ));
184+ }
185+ // Return calls in inlined functions should only break out of the scope of
186+ // the inlined code, not the entire function they are being inlined into. To
187+ // achieve this, make the call a non-return call and add a break. This does
188+ // not cause unbounded stack growth because inlining and return calling both
189+ // avoid creating a new stack frame.
190+ template <typename T> void handleReturnCall (T* curr, Type targetType) {
191+ curr->isReturn = false ;
192+ curr->type = targetType;
193+ if (isConcreteType (targetType)) {
194+ replaceCurrent (builder->makeBreak (returnName, curr));
195+ } else {
196+ replaceCurrent (builder->blockify (curr, builder->makeBreak (returnName)));
197+ }
198+ }
199+ void visitCall (Call* curr) {
200+ if (curr->isReturn ) {
201+ handleReturnCall (curr, module ->getFunction (curr->target )->result );
202+ }
203+ }
204+ void visitCallIndirect (CallIndirect* curr) {
205+ if (curr->isReturn ) {
206+ handleReturnCall (curr, module ->getFunctionType (curr->fullType )->result );
207+ }
208+ }
209+ void visitLocalGet (LocalGet* curr) {
210+ curr->index = localMapping[curr->index ];
211+ }
212+ void visitLocalSet (LocalSet* curr) {
213+ curr->index = localMapping[curr->index ];
214+ }
215+ };
216+
167217// Core inlining logic. Modifies the outside function (adding locals as
168218// needed), and returns the inlined code.
169219static Expression*
170220doInlining (Module* module , Function* into, InliningAction& action) {
171221 Function* from = action.contents ;
172222 auto * call = (*action.callSite )->cast <Call>();
223+ // Works for return_call, too
224+ Type retType = module ->getFunction (call->target )->result ;
173225 Builder builder (*module );
174- auto * block = Builder (* module ) .makeBlock ();
226+ auto * block = builder .makeBlock ();
175227 block->name = Name (std::string (" __inlined_func$" ) + from->name .str );
176- *action.callSite = block;
177- // Prepare to update the inlined code's locals and other things.
178- struct Updater : public PostWalker <Updater> {
179- std::map<Index, Index> localMapping;
180- Name returnName;
181- Builder* builder;
182-
183- void visitReturn (Return* curr) {
184- replaceCurrent (builder->makeBreak (returnName, curr->value ));
228+ if (call->isReturn ) {
229+ if (isConcreteType (retType)) {
230+ *action.callSite = builder.makeReturn (block);
231+ } else {
232+ *action.callSite = builder.makeSequence (block, builder.makeReturn ());
185233 }
186- void visitLocalGet (LocalGet* curr) {
187- curr->index = localMapping[curr->index ];
188- }
189- void visitLocalSet (LocalSet* curr) {
190- curr->index = localMapping[curr->index ];
191- }
192- } updater;
234+ } else {
235+ *action.callSite = block;
236+ }
237+ // Prepare to update the inlined code's locals and other things.
238+ Updater updater;
239+ updater.module = module ;
193240 updater.returnName = block->name ;
194241 updater.builder = &builder;
195242 // Set up a locals mapping
@@ -215,12 +262,12 @@ doInlining(Module* module, Function* into, InliningAction& action) {
215262 }
216263 updater.walk (contents);
217264 block->list .push_back (contents);
218- block->type = call-> type ;
265+ block->type = retType ;
219266 // If the function returned a value, we just set the block containing the
220267 // inlined code to have that type. or, if the function was void and
221268 // contained void, that is fine too. a bad case is a void function in which
222269 // we have unreachable code, so we would be replacing a void call with an
223- // unreachable; we need to handle
270+ // unreachable.
224271 if (contents->type == unreachable && block->type == none) {
225272 // Make the block reachable by adding a break to it
226273 block->list .push_back (builder.makeBreak (block->name ));
0 commit comments