@@ -17,6 +17,8 @@ use rustc_session::Session;
1717use smallvec:: SmallVec ;
1818use std:: mem;
1919
20+ type FunctionMap = FxHashMap < Word , Function > ;
21+
2022// FIXME(eddyb) this is a bit silly, but this keeps being repeated everywhere.
2123fn 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.
0 commit comments