Skip to content

Commit e3a607e

Browse files
authored
Cranelift: fix a bug with inlining and unreachable callee blocks (#11282)
We copy *all* callee blocks into the caller's layout, but were then only copying the callee instructions in *reachable* callee blocks into the caller. Therefore, any *unreachable* blocks would remain empty in the caller, which is invalid CLIF because all blocks must end in a terminator, so this commit adds a quick pass over the inlined blocks to remove any empty blocks from the caller's layout.
1 parent 98964d2 commit e3a607e

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

cranelift/codegen/src/inline.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,18 @@ fn inline_one(
414414
}
415415
}
416416

417+
// We copied *all* callee blocks into the caller's layout, but only copied
418+
// the callee instructions in *reachable* callee blocks into the caller's
419+
// associated blocks. Therefore, any *unreachable* blocks are empty in the
420+
// caller, which is invalid CLIF because all blocks must end in a
421+
// terminator, so do a quick pass over the inlined blocks and remove any
422+
// empty blocks from the caller's layout.
423+
for block in entity_map.iter_inlined_blocks(func) {
424+
if func.layout.first_inst(block).is_none() {
425+
func.layout.remove_block(block);
426+
}
427+
}
428+
417429
// Final step: fixup the exception tables of any inlined calls when we are
418430
// inlining a `try_call` site.
419431
//
@@ -1117,6 +1129,17 @@ impl EntityMap {
11171129
ir::Block::from_u32(offset + callee_block.as_u32())
11181130
}
11191131

1132+
fn iter_inlined_blocks(&self, func: &ir::Function) -> impl Iterator<Item = ir::Block> + use<> {
1133+
let start = self.block_offset.expect(
1134+
"must create inlined `ir::Block`s before calling `EntityMap::iter_inlined_blocks`",
1135+
);
1136+
1137+
let end = func.dfg.blocks.len();
1138+
let end = u32::try_from(end).unwrap();
1139+
1140+
(start..end).map(|i| ir::Block::from_u32(i))
1141+
}
1142+
11201143
fn inlined_global_value(&self, callee_global_value: ir::GlobalValue) -> ir::GlobalValue {
11211144
let offset = self
11221145
.global_value_offset
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
test inline precise-output
2+
target x86_64
3+
4+
function %f0(i32, i32) -> i32 {
5+
block0(v0: i32, v1: i32):
6+
v2 = iadd v0, v1
7+
return v2
8+
9+
;; This block is unreachable, despite it being in the function's layout! We
10+
;; should not include this block in callers' layouts when inlining because we
11+
;; will skip copying its instructions over into the caller due to how we use a
12+
;; reachability-based DFS traversal when inlining instructions. Ideally, we
13+
;; wouldn't even include it in callers' entity maps at all, but that is not
14+
;; required for correctness, while producing valid CLIF that does not have empty
15+
;; blocks without terminators is.
16+
block1:
17+
trap user42
18+
19+
}
20+
21+
; (no functions inlined into %f0)
22+
23+
function %f1() -> i32 {
24+
fn0 = %f0(i32, i32) -> i32
25+
block0():
26+
v0 = iconst.i32 10
27+
v1 = call fn0(v0, v0)
28+
return v1
29+
}
30+
31+
; function %f1() -> i32 fast {
32+
; sig0 = (i32, i32) -> i32 fast
33+
; fn0 = %f0 sig0
34+
;
35+
; block0:
36+
; v0 = iconst.i32 10
37+
; jump block1
38+
;
39+
; block1:
40+
; v3 = iadd.i32 v0, v0 ; v0 = 10, v0 = 10
41+
; jump block3(v3)
42+
;
43+
; block3(v2: i32):
44+
; v1 -> v2
45+
; return v1
46+
; }

0 commit comments

Comments
 (0)