@@ -8,11 +8,14 @@ use crate::qptr::{shapes, QPtrAttr, QPtrOp};
88use crate :: transform:: { InnerInPlaceTransform , Transformed , Transformer } ;
99use crate :: {
1010 spv, AddrSpace , AttrSet , AttrSetDef , Const , ConstCtor , ConstDef , Context , ControlNode ,
11- ControlNodeKind , DataInst , DataInstDef , DataInstForm , DataInstFormDef , DataInstKind , Diag ,
12- FuncDecl , GlobalVarDecl , OrdAssertEq , Type , TypeCtor , TypeCtorArg , TypeDef , Value ,
11+ ControlNodeKind , DataInst , DataInstDef , DataInstForm , DataInstFormDef , DataInstKind , DeclDef ,
12+ Diag , EntityOrientedDenseMap , FuncDecl , GlobalVarDecl , OrdAssertEq , Type , TypeCtor ,
13+ TypeCtorArg , TypeDef , Value ,
1314} ;
15+ use rustc_hash:: FxHashMap ;
1416use smallvec:: SmallVec ;
1517use std:: cell:: Cell ;
18+ use std:: mem;
1619use std:: num:: NonZeroU32 ;
1720use std:: rc:: Rc ;
1821
@@ -143,7 +146,13 @@ impl<'a> LowerFromSpvPtrs<'a> {
143146 // separately - so `LowerFromSpvPtrInstsInFunc` will leave all value defs
144147 // (including replaced instructions!) with unchanged `OpTypePointer`
145148 // types, that only `EraseSpvPtrs`, later, replaces with `QPtr`.
146- LowerFromSpvPtrInstsInFunc { lowerer : self } . in_place_transform_func_decl ( func_decl) ;
149+ LowerFromSpvPtrInstsInFunc {
150+ lowerer : self ,
151+ data_inst_use_counts : Default :: default ( ) ,
152+ remove_if_dead_inst_and_parent_block : Default :: default ( ) ,
153+ noop_offsets_to_base_ptr : Default :: default ( ) ,
154+ }
155+ . in_place_transform_func_decl ( func_decl) ;
147156 EraseSpvPtrs { lowerer : self } . in_place_transform_func_decl ( func_decl) ;
148157 }
149158
@@ -258,6 +267,19 @@ impl Transformer for EraseSpvPtrs<'_> {
258267
259268struct LowerFromSpvPtrInstsInFunc < ' a > {
260269 lowerer : & ' a LowerFromSpvPtrs < ' a > ,
270+
271+ // FIXME(eddyb) consider removing this and just do a full second traversal.
272+ data_inst_use_counts : EntityOrientedDenseMap < DataInst , NonZeroU32 > ,
273+
274+ // HACK(eddyb) this acts as a "queue" for `qptr`-producing instructions,
275+ // which may end up dead because they're unused (either unused originally,
276+ // in SPIR-V, or because of offset folding).
277+ remove_if_dead_inst_and_parent_block : Vec < ( DataInst , ControlNode ) > ,
278+
279+ // FIXME(eddyb) this is redundant with a few other things and only here
280+ // because it needs to be available from `transform_value`, which doesn't
281+ // have access to a `FuncAt` to look up anything.
282+ noop_offsets_to_base_ptr : FxHashMap < DataInst , Value > ,
261283}
262284
263285/// One `QPtr`->`QPtr` step used in the lowering of `Op*AccessChain`.
@@ -413,7 +435,7 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
413435 }
414436
415437 fn try_lower_data_inst_def (
416- & self ,
438+ & mut self ,
417439 mut func_at_data_inst : FuncAtMut < ' _ , DataInst > ,
418440 parent_block : ControlNode ,
419441 ) -> Result < Transformed < DataInstDef > , LowerError > {
@@ -438,18 +460,25 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
438460 _ => return Ok ( Transformed :: Unchanged ) ,
439461 } ;
440462
441- // Map `ptr` to its base & offset, if it points to a `QPtrOp::Offset`.
442- let ptr_to_base_ptr_and_offset = |ptr| match ptr {
443- Value :: DataInstOutput ( ptr_inst) => {
463+ // Flatten `QPtrOp::Offset`s behind `ptr` into a base pointer and offset.
464+ let flatten_offsets = |mut ptr| {
465+ let mut offset = 0 ;
466+ while let Value :: DataInstOutput ( ptr_inst) = ptr {
444467 let ptr_inst_def = func. at ( ptr_inst) . def ( ) ;
445468 match cx[ ptr_inst_def. form ] . kind {
446469 DataInstKind :: QPtr ( QPtrOp :: Offset ( ptr_offset) ) => {
447- Some ( ( ptr_inst_def. inputs [ 0 ] , ptr_offset) )
470+ match ptr_offset. checked_add ( offset) {
471+ Some ( combined_offset) => {
472+ ptr = ptr_inst_def. inputs [ 0 ] ;
473+ offset = combined_offset;
474+ }
475+ None => break ,
476+ }
448477 }
449- _ => None ,
478+ _ => break ,
450479 }
451480 }
452- _ => None ,
481+ ( ptr , offset )
453482 } ;
454483
455484 let replacement_kind_and_inputs = if spv_inst. opcode == wk. OpVariable {
@@ -478,7 +507,7 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
478507
479508 let ptr = data_inst_def. inputs [ 0 ] ;
480509
481- let ( ptr, offset) = ptr_to_base_ptr_and_offset ( ptr) . unwrap_or ( ( ptr , 0 ) ) ;
510+ let ( ptr, offset) = flatten_offsets ( ptr) ;
482511
483512 ( QPtrOp :: Load { offset } . into ( ) , [ ptr] . into_iter ( ) . collect ( ) )
484513 } else if spv_inst. opcode == wk. OpStore {
@@ -491,7 +520,7 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
491520 let ptr = data_inst_def. inputs [ 0 ] ;
492521 let value = data_inst_def. inputs [ 1 ] ;
493522
494- let ( ptr, offset) = ptr_to_base_ptr_and_offset ( ptr) . unwrap_or ( ( ptr , 0 ) ) ;
523+ let ( ptr, offset) = flatten_offsets ( ptr) ;
495524
496525 (
497526 QPtrOp :: Store { offset } . into ( ) ,
@@ -608,11 +637,10 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
608637 dyn_idx : None ,
609638 } ) = steps. first_mut ( )
610639 {
611- if let Some ( ( ptr_base_ptr, ptr_offset) ) = ptr_to_base_ptr_and_offset ( ptr) {
612- if let Some ( new_first_offset) = first_offset. checked_add ( ptr_offset) {
613- ptr = ptr_base_ptr;
614- * first_offset = new_first_offset;
615- }
640+ let ( ptr_base_ptr, ptr_offset) = flatten_offsets ( ptr) ;
641+ if let Some ( new_first_offset) = first_offset. checked_add ( ptr_offset) {
642+ ptr = ptr_base_ptr;
643+ * first_offset = new_first_offset;
616644 }
617645 }
618646
@@ -648,6 +676,11 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
648676 match & mut func. control_nodes [ parent_block] . kind {
649677 ControlNodeKind :: Block { insts } => {
650678 insts. insert_before ( step_data_inst, data_inst, func. data_insts ) ;
679+
680+ // HACK(eddyb) this tracking is kind of ad-hoc but should
681+ // easily cover everything we care about for now.
682+ self . remove_if_dead_inst_and_parent_block
683+ . push ( ( step_data_inst, parent_block) ) ;
651684 }
652685 _ => unreachable ! ( ) ,
653686 }
@@ -762,9 +795,51 @@ impl LowerFromSpvPtrInstsInFunc<'_> {
762795 func_at_data_inst. def ( ) . attrs = cx. intern ( attrs) ;
763796 }
764797 }
798+
799+ // FIXME(eddyb) these are only this whacky because an `u32` is being
800+ // encoded as `Option<NonZeroU32>` for (dense) map entry reasons.
801+ fn add_value_uses ( & mut self , values : & [ Value ] ) {
802+ for & v in values {
803+ if let Value :: DataInstOutput ( data_inst) = v {
804+ let count = self . data_inst_use_counts . entry ( data_inst) ;
805+ * count = Some (
806+ NonZeroU32 :: new ( count. map_or ( 0 , |c| c. get ( ) ) . checked_add ( 1 ) . unwrap ( ) ) . unwrap ( ) ,
807+ ) ;
808+ }
809+ }
810+ }
811+ fn remove_value_uses ( & mut self , values : & [ Value ] ) {
812+ for & v in values {
813+ if let Value :: DataInstOutput ( data_inst) = v {
814+ let count = self . data_inst_use_counts . entry ( data_inst) ;
815+ * count = NonZeroU32 :: new ( count. unwrap ( ) . get ( ) - 1 ) ;
816+ }
817+ }
818+ }
765819}
766820
767821impl Transformer for LowerFromSpvPtrInstsInFunc < ' _ > {
822+ // NOTE(eddyb) it's important that this only gets invoked on already lowered
823+ // `Value`s, so we can rely on e.g. `noop_offsets_to_base_ptr` being filled.
824+ fn transform_value_use ( & mut self , v : & Value ) -> Transformed < Value > {
825+ let mut v = * v;
826+
827+ let transformed = match v {
828+ Value :: DataInstOutput ( inst) => self
829+ . noop_offsets_to_base_ptr
830+ . get ( & inst)
831+ . copied ( )
832+ . map_or ( Transformed :: Unchanged , Transformed :: Changed ) ,
833+
834+ _ => Transformed :: Unchanged ,
835+ } ;
836+
837+ transformed. apply_to ( & mut v) ;
838+ self . add_value_uses ( & [ v] ) ;
839+
840+ transformed
841+ }
842+
768843 // HACK(eddyb) while we want to transform `DataInstDef`s, we can't inject
769844 // adjacent instructions without access to the parent `ControlNodeKind::Block`,
770845 // and to fix this would likely require list nodes to carry some handle to
@@ -777,16 +852,44 @@ impl Transformer for LowerFromSpvPtrInstsInFunc<'_> {
777852 & mut self ,
778853 mut func_at_control_node : FuncAtMut < ' _ , ControlNode > ,
779854 ) {
780- func_at_control_node
781- . reborrow ( )
782- . inner_in_place_transform_with ( self ) ;
783-
784855 let control_node = func_at_control_node. position ;
785856 if let ControlNodeKind :: Block { insts } = func_at_control_node. reborrow ( ) . def ( ) . kind {
786857 let mut func_at_inst_iter = func_at_control_node. reborrow ( ) . at ( insts) . into_iter ( ) ;
787858 while let Some ( mut func_at_inst) = func_at_inst_iter. next ( ) {
788859 match self . try_lower_data_inst_def ( func_at_inst. reborrow ( ) , control_node) {
789860 Ok ( Transformed :: Changed ( new_def) ) => {
861+ // HACK(eddyb) this tracking is kind of ad-hoc but should
862+ // easily cover everything we care about for now.
863+ if let DataInstKind :: QPtr ( op) = & self . lowerer . cx [ new_def. form ] . kind {
864+ match op {
865+ QPtrOp :: HandleArrayIndex
866+ | QPtrOp :: BufferData
867+ | QPtrOp :: BufferDynLen { .. }
868+ | QPtrOp :: Offset ( _)
869+ | QPtrOp :: DynOffset { .. } => {
870+ self . remove_if_dead_inst_and_parent_block
871+ . push ( ( func_at_inst. position , control_node) ) ;
872+ }
873+
874+ QPtrOp :: FuncLocalVar ( _)
875+ | QPtrOp :: Load { .. }
876+ | QPtrOp :: Store { .. } => { }
877+ }
878+
879+ if let QPtrOp :: Offset ( 0 ) = op {
880+ let mut base_ptr = new_def. inputs [ 0 ] ;
881+ if let Value :: DataInstOutput ( base_ptr_inst) = base_ptr {
882+ if let Some ( & base_ptr_base_ptr) =
883+ self . noop_offsets_to_base_ptr . get ( & base_ptr_inst)
884+ {
885+ base_ptr = base_ptr_base_ptr;
886+ }
887+ }
888+ self . noop_offsets_to_base_ptr
889+ . insert ( func_at_inst. position , base_ptr) ;
890+ }
891+ }
892+
790893 * func_at_inst. def ( ) = new_def;
791894 }
792895 result @ ( Ok ( Transformed :: Unchanged ) | Err ( _) ) => {
@@ -795,5 +898,41 @@ impl Transformer for LowerFromSpvPtrInstsInFunc<'_> {
795898 }
796899 }
797900 }
901+
902+ // NOTE(eddyb) this is done last so that `transform_value_use` only sees
903+ // the lowered `Value`s, not the original ones.
904+ func_at_control_node
905+ . reborrow ( )
906+ . inner_in_place_transform_with ( self ) ;
907+ }
908+
909+ fn in_place_transform_func_decl ( & mut self , func_decl : & mut FuncDecl ) {
910+ func_decl. inner_in_place_transform_with ( self ) ;
911+
912+ // Apply all `remove_if_dead_inst_and_parent_block` removals, that are truly unused.
913+ if let DeclDef :: Present ( func_def_body) = & mut func_decl. def {
914+ let remove_if_dead_inst_and_parent_block =
915+ mem:: take ( & mut self . remove_if_dead_inst_and_parent_block ) ;
916+ // NOTE(eddyb) reverse order is important, as each removal can reduce
917+ // use counts of an earlier definition, allowing further removal.
918+ for ( inst, parent_block) in remove_if_dead_inst_and_parent_block. into_iter ( ) . rev ( ) {
919+ if self . data_inst_use_counts . get ( inst) . is_none ( ) {
920+ // HACK(eddyb) can't really use helpers like `FuncAtMut::def`,
921+ // due to the need to borrow `control_nodes` and `data_insts`
922+ // at the same time - perhaps some kind of `FuncAtMut` position
923+ // types for "where a list is in a parent entity" could be used
924+ // to make this more ergonomic, although the potential need for
925+ // an actual list entity of its own, should be considered.
926+ match & mut func_def_body. control_nodes [ parent_block] . kind {
927+ ControlNodeKind :: Block { insts } => {
928+ insts. remove ( inst, & mut func_def_body. data_insts ) ;
929+ }
930+ _ => unreachable ! ( ) ,
931+ }
932+
933+ self . remove_value_uses ( & func_def_body. at ( inst) . def ( ) . inputs ) ;
934+ }
935+ }
936+ }
798937 }
799938}
0 commit comments