Skip to content

Commit f864a26

Browse files
committed
Revert "WIP: (TODO: finish bottom-up cleanups) bottom-up inlining"
This reverts commit 41ec7ea.
1 parent bd38148 commit f864a26

File tree

2 files changed

+82
-107
lines changed

2 files changed

+82
-107
lines changed

crates/rustc_codegen_spirv/src/linker/inline.rs

Lines changed: 77 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use rustc_session::Session;
1717
use smallvec::SmallVec;
1818
use std::mem;
1919

20+
type FunctionMap = FxHashMap<Word, Function>;
21+
2022
// FIXME(eddyb) this is a bit silly, but this keeps being repeated everywhere.
2123
fn next_id(header: &mut ModuleHeader) -> Word {
2224
let result = header.bound;
@@ -28,9 +30,6 @@ pub fn inline(sess: &Session, module: &mut Module) -> super::Result<()> {
2830
// This algorithm gets real sad if there's recursion - but, good news, SPIR-V bans recursion
2931
deny_recursion_in_module(sess, module)?;
3032

31-
// Compute the call-graph that will drive (inside-out, aka bottom-up) inlining.
32-
let (call_graph, func_id_to_idx) = CallGraph::collect_with_func_id_to_idx(module);
33-
3433
let custom_ext_inst_set_import = module
3534
.ext_inst_imports
3635
.iter()
@@ -40,7 +39,62 @@ pub fn inline(sess: &Session, module: &mut Module) -> super::Result<()> {
4039
})
4140
.map(|inst| inst.result_id.unwrap());
4241

43-
/*
42+
// HACK(eddyb) compute the set of functions that may `Abort` *transitively*,
43+
// which is only needed because of how we inline (sometimes it's outside-in,
44+
// aka top-down, instead of always being inside-out, aka bottom-up).
45+
//
46+
// (inlining is needed in the first place because our custom `Abort`
47+
// instructions get lowered to a simple `OpReturn` in entry-points, but
48+
// that requires that they get inlined all the way up to the entry-points)
49+
let functions_that_may_abort = custom_ext_inst_set_import
50+
.map(|custom_ext_inst_set_import| {
51+
let mut may_abort_by_id = FxHashSet::default();
52+
53+
// FIXME(eddyb) use this `CallGraph` abstraction more during inlining.
54+
let call_graph = CallGraph::collect(module);
55+
for func_idx in call_graph.post_order() {
56+
let func_id = module.functions[func_idx].def_id().unwrap();
57+
58+
let any_callee_may_abort = call_graph.callees[func_idx].iter().any(|&callee_idx| {
59+
may_abort_by_id.contains(&module.functions[callee_idx].def_id().unwrap())
60+
});
61+
if any_callee_may_abort {
62+
may_abort_by_id.insert(func_id);
63+
continue;
64+
}
65+
66+
let may_abort_directly = module.functions[func_idx].blocks.iter().any(|block| {
67+
match &block.instructions[..] {
68+
[.., last_normal_inst, terminator_inst]
69+
if last_normal_inst.class.opcode == Op::ExtInst
70+
&& last_normal_inst.operands[0].unwrap_id_ref()
71+
== custom_ext_inst_set_import
72+
&& CustomOp::decode_from_ext_inst(last_normal_inst)
73+
== CustomOp::Abort =>
74+
{
75+
assert_eq!(terminator_inst.class.opcode, Op::Unreachable);
76+
true
77+
}
78+
79+
_ => false,
80+
}
81+
});
82+
if may_abort_directly {
83+
may_abort_by_id.insert(func_id);
84+
}
85+
}
86+
87+
may_abort_by_id
88+
})
89+
.unwrap_or_default();
90+
91+
let functions = module
92+
.functions
93+
.iter()
94+
.map(|f| (f.def_id().unwrap(), f.clone()))
95+
.collect();
96+
let legal_globals = LegalGlobal::gather_from_module(module);
97+
4498
// Drop all the functions we'll be inlining. (This also means we won't waste time processing
4599
// inlines in functions that will get inlined)
46100
let mut dropped_ids = FxHashSet::default();
@@ -69,9 +123,6 @@ pub fn inline(sess: &Session, module: &mut Module) -> super::Result<()> {
69123
));
70124
}
71125
}
72-
*/
73-
74-
let legal_globals = LegalGlobal::gather_from_module(module);
75126

76127
let header = module.header.as_mut().unwrap();
77128
// FIXME(eddyb) clippy false positive (separate `map` required for borrowck).
@@ -103,8 +154,6 @@ pub fn inline(sess: &Session, module: &mut Module) -> super::Result<()> {
103154
id
104155
}),
105156

106-
func_id_to_idx,
107-
108157
id_to_name: module
109158
.debug_names
110159
.iter()
@@ -124,61 +173,22 @@ pub fn inline(sess: &Session, module: &mut Module) -> super::Result<()> {
124173
annotations: &mut module.annotations,
125174
types_global_values: &mut module.types_global_values,
126175

127-
legal_globals,
128-
129-
// NOTE(eddyb) this is needed because our custom `Abort` instructions get
130-
// lowered to a simple `OpReturn` in entry-points, but that requires that
131-
// they get inlined all the way up to the entry-points in the first place.
132-
functions_that_may_abort: module
133-
.functions
134-
.iter()
135-
.filter_map(|func| {
136-
let custom_ext_inst_set_import = custom_ext_inst_set_import?;
137-
func.blocks
138-
.iter()
139-
.any(|block| match &block.instructions[..] {
140-
[.., last_normal_inst, terminator_inst]
141-
if last_normal_inst.class.opcode == Op::ExtInst
142-
&& last_normal_inst.operands[0].unwrap_id_ref()
143-
== custom_ext_inst_set_import
144-
&& CustomOp::decode_from_ext_inst(last_normal_inst)
145-
== CustomOp::Abort =>
146-
{
147-
assert_eq!(terminator_inst.class.opcode, Op::Unreachable);
148-
true
149-
}
150-
151-
_ => false,
152-
})
153-
.then_some(func.def_id().unwrap())
154-
})
155-
.collect(),
176+
functions: &functions,
177+
legal_globals: &legal_globals,
178+
functions_that_may_abort: &functions_that_may_abort,
156179
};
157-
158-
let mut functions: Vec<_> = mem::take(&mut module.functions)
159-
.into_iter()
160-
.map(Ok)
161-
.collect();
162-
163-
// Inline functions in post-order (aka inside-out aka bottom-out) - that is,
164-
// callees are processed before their callers, to avoid duplicating work.
165-
for func_idx in call_graph.post_order() {
166-
let mut function = mem::replace(&mut functions[func_idx], Err(FuncIsBeingInlined)).unwrap();
167-
inliner.inline_fn(&mut function, &functions);
168-
fuse_trivial_branches(&mut function);
169-
functions[func_idx] = Ok(function);
180+
for function in &mut module.functions {
181+
inliner.inline_fn(function);
182+
fuse_trivial_branches(function);
170183
}
171184

172-
module.functions = functions.into_iter().map(|func| func.unwrap()).collect();
173-
174-
/*
175185
// Drop OpName etc. for inlined functions
176186
module.debug_names.retain(|inst| {
177187
!inst
178188
.operands
179189
.iter()
180190
.any(|op| op.id_ref_any().is_some_and(|id| dropped_ids.contains(&id)))
181-
});*/
191+
});
182192

183193
Ok(())
184194
}
@@ -446,27 +456,19 @@ fn should_inline(
446456
Ok(callee_control.contains(FunctionControl::INLINE))
447457
}
448458

449-
/// Helper error type for `Inliner`'s `functions` field, indicating a `Function`
450-
/// was taken out of its slot because it's being inlined.
451-
#[derive(Debug)]
452-
struct FuncIsBeingInlined;
453-
454459
// Steps:
455460
// Move OpVariable decls
456461
// Rewrite return
457462
// Renumber IDs
458463
// Insert blocks
459464

460-
struct Inliner<'m> {
465+
struct Inliner<'m, 'map> {
461466
/// ID of `OpExtInstImport` for our custom "extended instruction set"
462467
/// (see `crate::custom_insts` for more details).
463468
custom_ext_inst_set_import: Word,
464469

465470
op_type_void_id: Word,
466471

467-
/// Map from each function's ID to its index in `functions`.
468-
func_id_to_idx: FxHashMap<Word, usize>,
469-
470472
/// Pre-collected `OpName`s, that can be used to find any function's name
471473
/// during inlining (to be able to generate debuginfo that uses names).
472474
id_to_name: FxHashMap<Word, &'m str>,
@@ -483,12 +485,13 @@ struct Inliner<'m> {
483485
annotations: &'m mut Vec<Instruction>,
484486
types_global_values: &'m mut Vec<Instruction>,
485487

486-
legal_globals: FxHashMap<Word, LegalGlobal>,
487-
functions_that_may_abort: FxHashSet<Word>,
488+
functions: &'map FunctionMap,
489+
legal_globals: &'map FxHashMap<Word, LegalGlobal>,
490+
functions_that_may_abort: &'map FxHashSet<Word>,
488491
// rewrite_rules: FxHashMap<Word, Word>,
489492
}
490493

491-
impl Inliner<'_> {
494+
impl Inliner<'_, '_> {
492495
fn id(&mut self) -> Word {
493496
next_id(self.header)
494497
}
@@ -533,29 +536,19 @@ impl Inliner<'_> {
533536
inst_id
534537
}
535538

536-
fn inline_fn(
537-
&mut self,
538-
function: &mut Function,
539-
functions: &[Result<Function, FuncIsBeingInlined>],
540-
) {
539+
fn inline_fn(&mut self, function: &mut Function) {
541540
let mut block_idx = 0;
542541
while block_idx < function.blocks.len() {
543542
// If we successfully inlined a block, then repeat processing on the same block, in
544543
// case the newly inlined block has more inlined calls.
545544
// TODO: This is quadratic
546-
if !self.inline_block(function, block_idx, functions) {
547-
// TODO(eddyb) skip past the inlined callee without rescanning it.
545+
if !self.inline_block(function, block_idx) {
548546
block_idx += 1;
549547
}
550548
}
551549
}
552550

553-
fn inline_block(
554-
&mut self,
555-
caller: &mut Function,
556-
block_idx: usize,
557-
functions: &[Result<Function, FuncIsBeingInlined>],
558-
) -> bool {
551+
fn inline_block(&mut self, caller: &mut Function, block_idx: usize) -> bool {
559552
// Find the first inlined OpFunctionCall
560553
let call = caller.blocks[block_idx]
561554
.instructions
@@ -566,8 +559,8 @@ impl Inliner<'_> {
566559
(
567560
index,
568561
inst,
569-
functions[self.func_id_to_idx[&inst.operands[0].id_ref_any().unwrap()]]
570-
.as_ref()
562+
self.functions
563+
.get(&inst.operands[0].id_ref_any().unwrap())
571564
.unwrap(),
572565
)
573566
})
@@ -577,8 +570,8 @@ impl Inliner<'_> {
577570
call_inst: inst,
578571
};
579572
match should_inline(
580-
&self.legal_globals,
581-
&self.functions_that_may_abort,
573+
self.legal_globals,
574+
self.functions_that_may_abort,
582575
f,
583576
Some(call_site),
584577
) {
@@ -590,16 +583,6 @@ impl Inliner<'_> {
590583
None => return false,
591584
Some(call) => call,
592585
};
593-
594-
// Propagate "may abort" from callee to caller (i.e. as aborts get inlined).
595-
if self
596-
.functions_that_may_abort
597-
.contains(&callee.def_id().unwrap())
598-
{
599-
self.functions_that_may_abort
600-
.insert(caller.def_id().unwrap());
601-
}
602-
603586
let call_result_type = {
604587
let ty = call_inst.result_type.unwrap();
605588
if ty == self.op_type_void_id {
@@ -611,7 +594,6 @@ impl Inliner<'_> {
611594
let call_result_id = call_inst.result_id.unwrap();
612595

613596
// Get the debuginfo instructions that apply to the call.
614-
// TODO(eddyb) only one instruction should be necessary here w/ bottom-up.
615597
let custom_ext_inst_set_import = self.custom_ext_inst_set_import;
616598
let call_debug_insts = caller.blocks[block_idx].instructions[..call_index]
617599
.iter()
@@ -886,7 +868,6 @@ impl Inliner<'_> {
886868
..
887869
} = *self;
888870

889-
// TODO(eddyb) kill this as it shouldn't be needed for bottom-up inline.
890871
// HACK(eddyb) this is terrible, but we have to deal with it because of
891872
// how this inliner is outside-in, instead of inside-out, meaning that
892873
// context builds up "outside" of the callee blocks, inside the caller.

crates/rustc_codegen_spirv/src/linker/ipo.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use indexmap::IndexSet;
66
use rspirv::dr::Module;
7-
use rspirv::spirv::{Op, Word};
7+
use rspirv::spirv::Op;
88
use rustc_data_structures::fx::FxHashMap;
99

1010
// FIXME(eddyb) use newtyped indices and `IndexVec`.
@@ -19,9 +19,6 @@ pub struct CallGraph {
1919

2020
impl CallGraph {
2121
pub fn collect(module: &Module) -> Self {
22-
Self::collect_with_func_id_to_idx(module).0
23-
}
24-
pub fn collect_with_func_id_to_idx(module: &Module) -> (Self, FxHashMap<Word, FuncIdx>) {
2522
let func_id_to_idx: FxHashMap<_, _> = module
2623
.functions
2724
.iter()
@@ -54,13 +51,10 @@ impl CallGraph {
5451
.collect()
5552
})
5653
.collect();
57-
(
58-
Self {
59-
entry_points,
60-
callees,
61-
},
62-
func_id_to_idx,
63-
)
54+
Self {
55+
entry_points,
56+
callees,
57+
}
6458
}
6559

6660
/// Order functions using a post-order traversal, i.e. callees before callers.

0 commit comments

Comments
 (0)