Skip to content

Commit fd1e0af

Browse files
alexmalyshevmeta-codesync[bot]
authored andcommitted
Replace removeUnreachableBlocks with a targeted inliner fix
Summary: D110056992 fixed a JIT crash when inlining a callee with no reachable `return` by running `removeUnreachableBlocks` over the whole caller after the inlining loop. This replaces that with a more targeted fix at the point where the bad IR is produced. When `HIRBuilder::inlineHIR` merges the callee's returns into a single exit block, a callee with no reachable `return` leaves that exit block with no predecessors, so nothing flows into the merged `return_val`. We now define `return_val` with a `LoadConst<Bottom>` in that case, so the inliner's `Return` -> `Assign`+`Branch` rewrite never references an undefined register. The resulting dead blocks are dropped by the `CleanCFG` that already runs at the end of `InlineFunctionCalls::Run`, so the explicit `removeUnreachableBlocks(irfunc)` call is no longer needed. Reviewed By: mpage Differential Revision: D110145444 fbshipit-source-id: 9260014528d9f0b41ac37bef10ec8222c4847b65
1 parent 18479f8 commit fd1e0af

2 files changed

Lines changed: 25 additions & 14 deletions

File tree

cinderx/Jit/hir/builder.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -732,26 +732,37 @@ InlineResult HIRBuilder::inlineHIR(
732732
checkTranslate();
733733

734734
BasicBlock* entry_block = buildHIRImpl(caller, caller_frame_state);
735+
735736
// Make one block with a Return that merges the return branches from the
736-
// callee. After SSA, it will turn into a massive Phi. The caller can find
737+
// callee. After SSA, it will turn into a massive Phi. The caller can find
737738
// the Return and use it as the output of the call instruction.
738739
Register* return_val = caller->env.AllocateRegister();
739740
BasicBlock* exit_block = caller->cfg.AllocateBlock();
740-
if (preloader_.returnType() <= TPrimitive) {
741-
exit_block->append<Return>(return_val, preloader_.returnType());
742-
} else {
743-
exit_block->append<Return>(return_val);
744-
}
741+
size_t num_preds = 0;
745742
for (auto block : caller->cfg.GetRPOTraversal(entry_block)) {
746743
auto instr = block->GetTerminator();
747744
if (instr->IsReturn()) {
748745
auto assign = Assign::create(return_val, instr->GetOperand(0));
749746
auto branch = Branch::create(exit_block);
750747
instr->ExpandInto({assign, branch});
751748
delete instr;
749+
num_preds += 1;
752750
}
753751
}
754752

753+
// If the callee has no reachable return then the exit block is unreachable
754+
// and nothing flows into `return_val`. This will wreak havoc with later
755+
// optimization passes as they can't handle an undefined value, so define it
756+
// as Bottom.
757+
if (num_preds == 0) {
758+
exit_block->append<LoadConst>(return_val, TBottom);
759+
}
760+
if (preloader_.returnType() <= TPrimitive) {
761+
exit_block->append<Return>(return_val, preloader_.returnType());
762+
} else {
763+
exit_block->append<Return>(return_val);
764+
}
765+
755766
// Map of FrameState to parent pointers. We must completely disconnect the
756767
// inlined function's CFG from its caller for SSAify to run properly: it will
757768
// find uses (in FrameState) before defs and insert LoadConst<Nullptr>.

cinderx/Jit/hir/inliner.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ std::optional<InlineResult> inlineFunctionCall(
280280
}
281281
tail->push_front(EndInlinedFunction::create(begin_inlined_function));
282282

283-
// Transform LoadArg into Assign
283+
// Transform LoadArg into Assign. They'll only be in the entry block.
284284
for (auto it = result.entry->begin(); it != result.entry->end();) {
285285
auto& instr = *it;
286286
++it;
@@ -294,7 +294,8 @@ std::optional<InlineResult> inlineFunctionCall(
294294
}
295295
}
296296

297-
// Transform Return into Assign+Branch
297+
// Transform Return into Assign+Branch. The HIRBuilder guarantees that the
298+
// callee's exit block always has a Return, even if it is unreachable.
298299
auto return_instr = result.exit->GetTerminator();
299300
JIT_CHECK(
300301
return_instr->IsReturn(),
@@ -603,12 +604,11 @@ void InlineFunctionCalls::Run(Function& irfunc) {
603604
enqueueCandidates(call_code, funcFullname(call.func), nested);
604605
}
605606

606-
// The inliner will make some blocks unreachable and we need to remove them to
607-
// make the CFG valid again. While inlining might make some blocks
608-
// unreachable and therefore make less work (less to inline), we cannot remove
609-
// unreachable blocks in the above loop. It might delete instructions pointed
610-
// to by `calls`.
611-
removeUnreachableBlocks(irfunc);
607+
// Inlining a callee with no reachable return leaves unreachable blocks
608+
// behind. We can't drop them inside the loop above (that might free call
609+
// instructions still queued for inlining), so we clean up here once the loop
610+
// is done. CleanCFG removes the unreachable blocks; CopyPropagation first
611+
// collapses the Assigns the inliner introduced.
612612
CopyPropagation{}.Run(irfunc);
613613
CleanCFG{}.Run(irfunc);
614614
}

0 commit comments

Comments
 (0)