Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit aa73fbc

Browse files
committed
qptr/lower: more aggressively strip Offset(0) and remove unused instructions.
1 parent dde3708 commit aa73fbc

1 file changed

Lines changed: 160 additions & 21 deletions

File tree

src/qptr/lower.rs

Lines changed: 160 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ use crate::qptr::{shapes, QPtrAttr, QPtrOp};
88
use crate::transform::{InnerInPlaceTransform, Transformed, Transformer};
99
use 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;
1416
use smallvec::SmallVec;
1517
use std::cell::Cell;
18+
use std::mem;
1619
use std::num::NonZeroU32;
1720
use 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

259268
struct 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

767821
impl 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

Comments
 (0)