1use super::layout::*;
5
6use crate::func_at::FuncAtMut;
7use crate::qptr::{QPtrAttr, QPtrMemUsage, QPtrMemUsageKind, QPtrOp, QPtrUsage, shapes};
8use crate::transform::{InnerInPlaceTransform, InnerTransform, Transformed, Transformer};
9use crate::{
10 AddrSpace, Attr, AttrSet, AttrSetDef, Const, ConstDef, ConstKind, Context, ControlNode,
11 ControlNodeKind, DataInst, DataInstDef, DataInstFormDef, DataInstKind, DeclDef, Diag,
12 DiagLevel, EntityDefs, EntityOrientedDenseMap, Func, FuncDecl, FxIndexMap, GlobalVar,
13 GlobalVarDecl, Module, Type, TypeDef, TypeKind, TypeOrConst, Value, spv,
14};
15use smallvec::SmallVec;
16use std::cell::Cell;
17use std::mem;
18use std::num::NonZeroU32;
19use std::rc::Rc;
20
21struct LiftError(Diag);
22
23pub struct LiftToSpvPtrs<'a> {
27 cx: Rc<Context>,
28 wk: &'static spv::spec::WellKnown,
29 layout_cache: LayoutCache<'a>,
30
31 cached_u32_type: Cell<Option<Type>>,
32}
33
34impl<'a> LiftToSpvPtrs<'a> {
35 pub fn new(cx: Rc<Context>, layout_config: &'a LayoutConfig) -> Self {
36 Self {
37 cx: cx.clone(),
38 wk: &spv::spec::Spec::get().well_known,
39 layout_cache: LayoutCache::new(cx, layout_config),
40 cached_u32_type: Default::default(),
41 }
42 }
43
44 pub fn lift_global_var(&self, global_var_decl: &mut GlobalVarDecl) {
45 match self.spv_ptr_type_and_addr_space_for_global_var(global_var_decl) {
46 Ok((spv_ptr_type, addr_space)) => {
47 global_var_decl.attrs = self.strip_qptr_usage_attr(global_var_decl.attrs);
48 global_var_decl.type_of_ptr_to = spv_ptr_type;
49 global_var_decl.addr_space = addr_space;
50 global_var_decl.shape = None;
51 }
52 Err(LiftError(e)) => {
53 global_var_decl.attrs.push_diag(&self.cx, e);
54 }
55 }
56 }
60
61 pub fn lift_all_funcs(&self, module: &mut Module, funcs: impl IntoIterator<Item = Func>) {
62 for func in funcs {
63 LiftToSpvPtrInstsInFunc {
64 lifter: self,
65 global_vars: &module.global_vars,
66
67 deferred_ptr_noops: Default::default(),
68 data_inst_use_counts: Default::default(),
69
70 func_has_qptr_analysis_bug_diags: false,
71 }
72 .in_place_transform_func_decl(&mut module.funcs[func]);
73 }
74 }
75
76 fn find_qptr_usage_attr(&self, attrs: AttrSet) -> Result<&QPtrUsage, LiftError> {
77 self.cx[attrs]
78 .attrs
79 .iter()
80 .find_map(|attr| match attr {
81 Attr::QPtr(QPtrAttr::Usage(usage)) => Some(&usage.0),
82 _ => None,
83 })
84 .ok_or_else(|| LiftError(Diag::bug(["missing `qptr.usage` attribute".into()])))
85 }
86
87 fn strip_qptr_usage_attr(&self, attrs: AttrSet) -> AttrSet {
88 self.cx.intern(AttrSetDef {
89 attrs: self.cx[attrs]
90 .attrs
91 .iter()
92 .filter(|attr| !matches!(attr, Attr::QPtr(QPtrAttr::Usage(_))))
93 .cloned()
94 .collect(),
95 })
96 }
97
98 fn spv_ptr_type_and_addr_space_for_global_var(
99 &self,
100 global_var_decl: &GlobalVarDecl,
101 ) -> Result<(Type, AddrSpace), LiftError> {
102 let wk = self.wk;
103
104 let qptr_usage = self.find_qptr_usage_attr(global_var_decl.attrs)?;
105
106 let shape =
107 global_var_decl.shape.ok_or_else(|| LiftError(Diag::bug(["missing shape".into()])))?;
108 let (storage_class, pointee_type) = match (global_var_decl.addr_space, shape) {
109 (AddrSpace::Handles, shapes::GlobalVarShape::Handles { handle, fixed_count }) => {
110 let (storage_class, handle_type) = match handle {
111 shapes::Handle::Opaque(ty) => {
112 if self.pointee_type_for_usage(qptr_usage)? != ty {
113 return Err(LiftError(Diag::bug([
114 "mismatched opaque handle types in `qptr.usage` vs `shape`".into(),
115 ])));
116 }
117 (wk.UniformConstant, ty)
118 }
119 shapes::Handle::Buffer(AddrSpace::SpvStorageClass(storage_class), _buf) => {
122 (storage_class, self.pointee_type_for_usage(qptr_usage)?)
123 }
124 shapes::Handle::Buffer(AddrSpace::Handles, _) => {
125 return Err(LiftError(Diag::bug([
126 "invalid `AddrSpace::Handles` in `Handle::Buffer`".into(),
127 ])));
128 }
129 };
130 (
131 storage_class,
132 if fixed_count == Some(NonZeroU32::new(1).unwrap()) {
133 handle_type
134 } else {
135 self.spv_op_type_array(handle_type, fixed_count.map(|c| c.get()), None)?
136 },
137 )
138 }
139 (
142 AddrSpace::SpvStorageClass(storage_class),
143 shapes::GlobalVarShape::UntypedData(_layout),
144 ) => (storage_class, self.pointee_type_for_usage(qptr_usage)?),
145 (
146 AddrSpace::SpvStorageClass(storage_class),
147 shapes::GlobalVarShape::TypedInterface(ty),
148 ) => (storage_class, ty),
149
150 (
151 AddrSpace::Handles,
152 shapes::GlobalVarShape::UntypedData(_) | shapes::GlobalVarShape::TypedInterface(_),
153 )
154 | (AddrSpace::SpvStorageClass(_), shapes::GlobalVarShape::Handles { .. }) => {
155 return Err(LiftError(Diag::bug(["mismatched `addr_space` and `shape`".into()])));
156 }
157 };
158 let addr_space = AddrSpace::SpvStorageClass(storage_class);
159 Ok((self.spv_ptr_type(addr_space, pointee_type), addr_space))
160 }
161
162 fn as_spv_ptr_type(&self, ty: Type) -> Option<(AddrSpace, Type)> {
166 match &self.cx[ty].kind {
167 TypeKind::SpvInst { spv_inst, type_and_const_inputs }
168 if spv_inst.opcode == self.wk.OpTypePointer =>
169 {
170 let sc = match spv_inst.imms[..] {
171 [spv::Imm::Short(_, sc)] => sc,
172 _ => unreachable!(),
173 };
174 let pointee = match type_and_const_inputs[..] {
175 [TypeOrConst::Type(elem_type)] => elem_type,
176 _ => unreachable!(),
177 };
178 Some((AddrSpace::SpvStorageClass(sc), pointee))
179 }
180 _ => None,
181 }
182 }
183
184 fn spv_ptr_type(&self, addr_space: AddrSpace, pointee_type: Type) -> Type {
185 let wk = self.wk;
186
187 let storage_class = match addr_space {
188 AddrSpace::Handles => unreachable!(),
189 AddrSpace::SpvStorageClass(storage_class) => storage_class,
190 };
191 self.cx.intern(TypeKind::SpvInst {
192 spv_inst: spv::Inst {
193 opcode: wk.OpTypePointer,
194 imms: [spv::Imm::Short(wk.StorageClass, storage_class)].into_iter().collect(),
195 },
196 type_and_const_inputs: [TypeOrConst::Type(pointee_type)].into_iter().collect(),
197 })
198 }
199
200 fn pointee_type_for_usage(&self, usage: &QPtrUsage) -> Result<Type, LiftError> {
201 let wk = self.wk;
202
203 match usage {
204 &QPtrUsage::Handles(shapes::Handle::Opaque(ty)) => Ok(ty),
205 QPtrUsage::Handles(shapes::Handle::Buffer(_, data_usage)) => {
206 let attr_spv_decorate_block = Attr::SpvAnnotation(spv::Inst {
207 opcode: wk.OpDecorate,
208 imms: [spv::Imm::Short(wk.Decoration, wk.Block)].into_iter().collect(),
209 });
210 match &data_usage.kind {
211 QPtrMemUsageKind::Unused => {
212 self.spv_op_type_struct([], [attr_spv_decorate_block])
213 }
214 QPtrMemUsageKind::OffsetBase(fields) => self.spv_op_type_struct(
215 fields.iter().map(|(&field_offset, field_usage)| {
216 Ok((field_offset, self.pointee_type_for_mem_usage(field_usage)?))
217 }),
218 [attr_spv_decorate_block],
219 ),
220 QPtrMemUsageKind::StrictlyTyped(_)
221 | QPtrMemUsageKind::DirectAccess(_)
222 | QPtrMemUsageKind::DynOffsetBase { .. } => self.spv_op_type_struct(
223 [Ok((0, self.pointee_type_for_mem_usage(data_usage)?))],
224 [attr_spv_decorate_block],
225 ),
226 }
227 }
228 QPtrUsage::Memory(usage) => self.pointee_type_for_mem_usage(usage),
229 }
230 }
231
232 fn pointee_type_for_mem_usage(&self, usage: &QPtrMemUsage) -> Result<Type, LiftError> {
233 match &usage.kind {
234 QPtrMemUsageKind::Unused => self.spv_op_type_struct([], []),
235 &QPtrMemUsageKind::StrictlyTyped(ty) | &QPtrMemUsageKind::DirectAccess(ty) => Ok(ty),
236 QPtrMemUsageKind::OffsetBase(fields) => self.spv_op_type_struct(
237 fields.iter().map(|(&field_offset, field_usage)| {
238 Ok((field_offset, self.pointee_type_for_mem_usage(field_usage)?))
239 }),
240 [],
241 ),
242 QPtrMemUsageKind::DynOffsetBase { element, stride } => {
243 let element_type = self.pointee_type_for_mem_usage(element)?;
244
245 let fixed_len = usage
246 .max_size
247 .map(|size| {
248 if size % stride.get() != 0 {
249 return Err(LiftError(Diag::bug([format!(
250 "DynOffsetBase: size ({size}) not a multiple of stride ({stride})"
251 )
252 .into()])));
253 }
254 Ok(size / stride.get())
255 })
256 .transpose()?;
257
258 self.spv_op_type_array(element_type, fixed_len, Some(*stride))
259 }
260 }
261 }
262
263 fn spv_op_type_array(
264 &self,
265 element_type: Type,
266 fixed_len: Option<u32>,
267 stride: Option<NonZeroU32>,
268 ) -> Result<Type, LiftError> {
269 let wk = self.wk;
270
271 let stride_attrs = stride.map(|stride| {
272 self.cx.intern(AttrSetDef {
273 attrs: [Attr::SpvAnnotation(spv::Inst {
274 opcode: wk.OpDecorate,
275 imms: [
276 spv::Imm::Short(wk.Decoration, wk.ArrayStride),
277 spv::Imm::Short(wk.LiteralInteger, stride.get()),
278 ]
279 .into_iter()
280 .collect(),
281 })]
282 .into(),
283 })
284 });
285
286 let spv_opcode = if fixed_len.is_some() { wk.OpTypeArray } else { wk.OpTypeRuntimeArray };
287
288 Ok(self.cx.intern(TypeDef {
289 attrs: stride_attrs.unwrap_or_default(),
290 kind: TypeKind::SpvInst {
291 spv_inst: spv_opcode.into(),
292 type_and_const_inputs: [TypeOrConst::Type(element_type)]
293 .into_iter()
294 .chain(fixed_len.map(|len| TypeOrConst::Const(self.const_u32(len))))
295 .collect(),
296 },
297 }))
298 }
299
300 fn spv_op_type_struct(
301 &self,
302 field_offsets_and_types: impl IntoIterator<Item = Result<(u32, Type), LiftError>>,
303 extra_attrs: impl IntoIterator<Item = Attr>,
304 ) -> Result<Type, LiftError> {
305 let wk = self.wk;
306
307 let field_offsets_and_types = field_offsets_and_types.into_iter();
308 let mut attrs = AttrSetDef::default();
309 let mut type_and_const_inputs =
310 SmallVec::with_capacity(field_offsets_and_types.size_hint().0);
311 for (i, field_offset_and_type) in field_offsets_and_types.enumerate() {
312 let (offset, field_type) = field_offset_and_type?;
313 attrs.attrs.insert(Attr::SpvAnnotation(spv::Inst {
314 opcode: wk.OpMemberDecorate,
315 imms: [
316 spv::Imm::Short(wk.LiteralInteger, i.try_into().unwrap()),
317 spv::Imm::Short(wk.Decoration, wk.Offset),
318 spv::Imm::Short(wk.LiteralInteger, offset),
319 ]
320 .into_iter()
321 .collect(),
322 }));
323 type_and_const_inputs.push(TypeOrConst::Type(field_type));
324 }
325 attrs.attrs.extend(extra_attrs);
326 Ok(self.cx.intern(TypeDef {
327 attrs: self.cx.intern(attrs),
328 kind: TypeKind::SpvInst { spv_inst: wk.OpTypeStruct.into(), type_and_const_inputs },
329 }))
330 }
331
332 fn u32_type(&self) -> Type {
334 if let Some(cached) = self.cached_u32_type.get() {
335 return cached;
336 }
337 let wk = self.wk;
338 let ty = self.cx.intern(TypeKind::SpvInst {
339 spv_inst: spv::Inst {
340 opcode: wk.OpTypeInt,
341 imms: [
342 spv::Imm::Short(wk.LiteralInteger, 32),
343 spv::Imm::Short(wk.LiteralInteger, 0),
344 ]
345 .into_iter()
346 .collect(),
347 },
348 type_and_const_inputs: [].into_iter().collect(),
349 });
350 self.cached_u32_type.set(Some(ty));
351 ty
352 }
353
354 fn const_u32(&self, x: u32) -> Const {
355 let wk = self.wk;
356
357 self.cx.intern(ConstDef {
358 attrs: AttrSet::default(),
359 ty: self.u32_type(),
360 kind: ConstKind::SpvInst {
361 spv_inst_and_const_inputs: Rc::new((
362 spv::Inst {
363 opcode: wk.OpConstant,
364 imms: [spv::Imm::Short(wk.LiteralContextDependentNumber, x)]
365 .into_iter()
366 .collect(),
367 },
368 [].into_iter().collect(),
369 )),
370 },
371 })
372 }
373
374 fn layout_of(&self, ty: Type) -> Result<TypeLayout, LiftError> {
376 self.layout_cache.layout_of(ty).map_err(|LayoutError(err)| LiftError(err))
377 }
378}
379
380struct LiftToSpvPtrInstsInFunc<'a> {
381 lifter: &'a LiftToSpvPtrs<'a>,
382 global_vars: &'a EntityDefs<GlobalVar>,
383
384 deferred_ptr_noops: FxIndexMap<DataInst, DeferredPtrNoop>,
396
397 data_inst_use_counts: EntityOrientedDenseMap<DataInst, NonZeroU32>,
399
400 func_has_qptr_analysis_bug_diags: bool,
402}
403
404struct DeferredPtrNoop {
405 output_pointer: Value,
406
407 output_pointer_addr_space: AddrSpace,
408
409 output_pointee_layout: TypeLayout,
412
413 parent_block: ControlNode,
414}
415
416impl LiftToSpvPtrInstsInFunc<'_> {
417 fn try_lift_data_inst_def(
418 &mut self,
419 mut func_at_data_inst: FuncAtMut<'_, DataInst>,
420 parent_block: ControlNode,
421 ) -> Result<Transformed<DataInstDef>, LiftError> {
422 let wk = self.lifter.wk;
423 let cx = &self.lifter.cx;
424
425 let func_at_data_inst_frozen = func_at_data_inst.reborrow().freeze();
426 let data_inst = func_at_data_inst_frozen.position;
427 let data_inst_def = func_at_data_inst_frozen.def();
428 let data_inst_form_def = &cx[data_inst_def.form];
429 let func = func_at_data_inst_frozen.at(());
430 let type_of_val = |v: Value| func.at(v).type_of(cx);
431 let type_of_val_as_spv_ptr_with_layout = |v: Value| {
434 if let Value::DataInstOutput(v_data_inst) = v {
435 if let Some(ptr_noop) = self.deferred_ptr_noops.get(&v_data_inst) {
436 return Ok((
437 ptr_noop.output_pointer_addr_space,
438 ptr_noop.output_pointee_layout.clone(),
439 ));
440 }
441 }
442
443 let (addr_space, pointee_type) =
444 self.lifter.as_spv_ptr_type(type_of_val(v)).ok_or_else(|| {
445 LiftError(Diag::bug(["pointer input not an `OpTypePointer`".into()]))
446 })?;
447
448 Ok((addr_space, self.lifter.layout_of(pointee_type)?))
449 };
450 let replacement_data_inst_def = match &data_inst_form_def.kind {
451 &DataInstKind::FuncCall(_callee) => {
452 for &v in &data_inst_def.inputs {
453 if self.lifter.as_spv_ptr_type(type_of_val(v)).is_some() {
454 return Err(LiftError(Diag::bug([
455 "unimplemented calls with pointer args".into(),
456 ])));
457 }
458 }
459 return Ok(Transformed::Unchanged);
460 }
461
462 DataInstKind::QPtr(QPtrOp::FuncLocalVar(_mem_layout)) => {
463 let qptr_usage = self.lifter.find_qptr_usage_attr(data_inst_def.attrs)?;
464
465 let pointee_type = self.lifter.pointee_type_for_usage(qptr_usage)?;
467 DataInstDef {
468 attrs: self.lifter.strip_qptr_usage_attr(data_inst_def.attrs),
469 form: cx.intern(DataInstFormDef {
470 kind: DataInstKind::SpvInst(spv::Inst {
471 opcode: wk.OpVariable,
472 imms: [spv::Imm::Short(wk.StorageClass, wk.Function)]
473 .into_iter()
474 .collect(),
475 }),
476 output_type: Some(
477 self.lifter.spv_ptr_type(
478 AddrSpace::SpvStorageClass(wk.Function),
479 pointee_type,
480 ),
481 ),
482 }),
483 inputs: data_inst_def.inputs.clone(),
484 }
485 }
486 DataInstKind::QPtr(QPtrOp::HandleArrayIndex) => {
487 let (addr_space, layout) =
488 type_of_val_as_spv_ptr_with_layout(data_inst_def.inputs[0])?;
489 let handle = match layout {
490 TypeLayout::HandleArray(handle, _) => handle,
492 TypeLayout::Handle(_) => {
493 return Err(LiftError(Diag::bug(["cannot index single Handle".into()])));
494 }
495 TypeLayout::Concrete(_) => {
496 return Err(LiftError(Diag::bug(
497 ["cannot index memory as handles".into()],
498 )));
499 }
500 };
501 let handle_type = match handle {
502 shapes::Handle::Opaque(ty) => ty,
503 shapes::Handle::Buffer(_, buf) => buf.original_type,
504 };
505 DataInstDef {
506 attrs: data_inst_def.attrs,
507 form: cx.intern(DataInstFormDef {
508 kind: DataInstKind::SpvInst(wk.OpAccessChain.into()),
509 output_type: Some(self.lifter.spv_ptr_type(addr_space, handle_type)),
510 }),
511 inputs: data_inst_def.inputs.clone(),
512 }
513 }
514 DataInstKind::QPtr(QPtrOp::BufferData) => {
515 let buf_ptr = data_inst_def.inputs[0];
516 let (addr_space, buf_layout) = type_of_val_as_spv_ptr_with_layout(buf_ptr)?;
517
518 let buf_data_layout = match buf_layout {
519 TypeLayout::Handle(shapes::Handle::Buffer(_, buf)) => TypeLayout::Concrete(buf),
520 _ => return Err(LiftError(Diag::bug(["non-Buffer pointee".into()]))),
521 };
522
523 self.deferred_ptr_noops.insert(data_inst, DeferredPtrNoop {
524 output_pointer: buf_ptr,
525 output_pointer_addr_space: addr_space,
526 output_pointee_layout: buf_data_layout,
527 parent_block,
528 });
529
530 DataInstDef {
531 form: cx.intern(DataInstFormDef {
535 kind: QPtrOp::BufferData.into(),
536 output_type: Some(type_of_val(buf_ptr)),
537 }),
538 ..data_inst_def.clone()
539 }
540 }
541 &DataInstKind::QPtr(QPtrOp::BufferDynLen { fixed_base_size, dyn_unit_stride }) => {
542 let buf_ptr = data_inst_def.inputs[0];
543 let (_, buf_layout) = type_of_val_as_spv_ptr_with_layout(buf_ptr)?;
544
545 let buf_data_layout = match buf_layout {
546 TypeLayout::Handle(shapes::Handle::Buffer(_, buf)) => buf,
547 _ => return Err(LiftError(Diag::bug(["non-Buffer pointee".into()]))),
548 };
549
550 let field_idx = match &buf_data_layout.components {
551 Components::Fields { offsets, layouts }
552 if offsets.last() == Some(&fixed_base_size)
553 && layouts.last().map_or(false, |last_field| {
554 last_field.mem_layout.fixed_base.size == 0
555 && last_field.mem_layout.dyn_unit_stride
556 == Some(dyn_unit_stride)
557 && matches!(last_field.components, Components::Elements {
558 fixed_len: None,
559 ..
560 })
561 }) =>
562 {
563 u32::try_from(offsets.len() - 1).unwrap()
564 }
565 _ => {
567 return Err(LiftError(Diag::bug([
568 "buffer data type shape mismatch".into()
569 ])));
570 }
571 };
572
573 DataInstDef {
574 form: cx.intern(DataInstFormDef {
575 kind: DataInstKind::SpvInst(spv::Inst {
576 opcode: wk.OpArrayLength,
577 imms: [spv::Imm::Short(wk.LiteralInteger, field_idx)]
578 .into_iter()
579 .collect(),
580 }),
581 output_type: data_inst_form_def.output_type,
582 }),
583 ..data_inst_def.clone()
584 }
585 }
586 &DataInstKind::QPtr(QPtrOp::Offset(offset)) => {
587 let base_ptr = data_inst_def.inputs[0];
588 let (addr_space, layout) = type_of_val_as_spv_ptr_with_layout(base_ptr)?;
589 let mut layout = match layout {
590 TypeLayout::Handle(_) | TypeLayout::HandleArray(..) => {
591 return Err(LiftError(Diag::bug(["cannot offset Handles".into()])));
592 }
593 TypeLayout::Concrete(mem_layout) => mem_layout,
594 };
595 let mut offset = u32::try_from(offset)
596 .ok()
597 .ok_or_else(|| LiftError(Diag::bug(["negative offset".into()])))?;
598
599 let mut access_chain_inputs: SmallVec<_> = [base_ptr].into_iter().collect();
600 while offset > 0 {
602 let idx = {
603 let allow_zst = false;
608 let offset_range = if allow_zst {
609 offset..offset
610 } else {
611 offset..offset.saturating_add(1)
612 };
613 let mut component_indices =
614 layout.components.find_components_containing(offset_range);
615 match (component_indices.next(), component_indices.next()) {
616 (None, _) => {
617 return Err(LiftError(Diag::bug([format!(
620 "offset {offset} not found in type layout, after {} access chain indices",
621 access_chain_inputs.len() - 1
622 ).into()])));
623 }
624 (Some(idx), Some(_)) => {
625 if allow_zst {
627 return Err(LiftError(Diag::bug([
628 "ambiguity due to ZSTs in type layout".into(),
629 ])));
630 }
631 idx
633 }
634 (Some(idx), None) => idx,
635 }
636 };
637
638 let idx_as_i32 = i32::try_from(idx).ok().ok_or_else(|| {
639 LiftError(Diag::bug([
640 format!("{idx} not representable as a positive s32").into()
641 ]))
642 })?;
643 access_chain_inputs
644 .push(Value::Const(self.lifter.const_u32(idx_as_i32 as u32)));
645
646 match &layout.components {
647 Components::Scalar => unreachable!(),
648 Components::Elements { stride, elem, .. } => {
649 offset %= stride.get();
650 layout = elem.clone();
651 }
652 Components::Fields { offsets, layouts } => {
653 offset -= offsets[idx];
654 layout = layouts[idx].clone();
655 }
656 }
657 }
658
659 if access_chain_inputs.len() == 1 {
660 self.deferred_ptr_noops.insert(data_inst, DeferredPtrNoop {
661 output_pointer: base_ptr,
662 output_pointer_addr_space: addr_space,
663 output_pointee_layout: TypeLayout::Concrete(layout),
664 parent_block,
665 });
666 DataInstDef {
667 form: cx.intern(DataInstFormDef {
671 kind: QPtrOp::Offset(0).into(),
672 output_type: Some(type_of_val(base_ptr)),
673 }),
674 ..data_inst_def.clone()
675 }
676 } else {
677 DataInstDef {
678 attrs: data_inst_def.attrs,
679 form: cx.intern(DataInstFormDef {
680 kind: DataInstKind::SpvInst(wk.OpAccessChain.into()),
681 output_type: Some(
682 self.lifter.spv_ptr_type(addr_space, layout.original_type),
683 ),
684 }),
685 inputs: access_chain_inputs,
686 }
687 }
688 }
689 DataInstKind::QPtr(QPtrOp::DynOffset { stride, index_bounds }) => {
690 let base_ptr = data_inst_def.inputs[0];
691 let (addr_space, layout) = type_of_val_as_spv_ptr_with_layout(base_ptr)?;
692 let mut layout = match layout {
693 TypeLayout::Handle(_) | TypeLayout::HandleArray(..) => {
694 return Err(LiftError(Diag::bug(["cannot offset Handles".into()])));
695 }
696 TypeLayout::Concrete(mem_layout) => mem_layout,
697 };
698
699 let mut access_chain_inputs: SmallVec<_> = [base_ptr].into_iter().collect();
700 loop {
701 if let Components::Elements { stride: layout_stride, elem, fixed_len } =
702 &layout.components
703 {
704 if layout_stride == stride
705 && Ok(index_bounds.clone())
706 == fixed_len
707 .map(|len| i32::try_from(len.get()).map(|len| 0..len))
708 .transpose()
709 {
710 access_chain_inputs.push(data_inst_def.inputs[1]);
711 layout = elem.clone();
712 break;
713 }
714 }
715
716 let idx = {
718 let min_expected_len = index_bounds
723 .clone()
724 .and_then(|index_bounds| u32::try_from(index_bounds.end).ok())
725 .unwrap_or(0);
726 let offset_range =
727 0..min_expected_len.checked_add(stride.get()).unwrap_or(0);
728 let mut component_indices =
729 layout.components.find_components_containing(offset_range);
730 match (component_indices.next(), component_indices.next()) {
731 (None, _) => {
732 return Err(LiftError(Diag::bug([
733 "matching array not found in pointee type layout".into(),
734 ])));
735 }
736 (Some(_), Some(_)) => {
740 return Err(LiftError(Diag::bug([
741 "ambiguity due to ZSTs in pointee type layout".into(),
742 ])));
743 }
744 (Some(idx), None) => idx,
745 }
746 };
747
748 let idx_as_i32 = i32::try_from(idx).ok().ok_or_else(|| {
749 LiftError(Diag::bug([
750 format!("{idx} not representable as a positive s32").into()
751 ]))
752 })?;
753 access_chain_inputs
754 .push(Value::Const(self.lifter.const_u32(idx_as_i32 as u32)));
755
756 layout = match &layout.components {
757 Components::Scalar => unreachable!(),
758 Components::Elements { elem, .. } => elem.clone(),
759 Components::Fields { layouts, .. } => layouts[idx].clone(),
760 };
761 }
762 DataInstDef {
763 attrs: data_inst_def.attrs,
764 form: cx.intern(DataInstFormDef {
765 kind: DataInstKind::SpvInst(wk.OpAccessChain.into()),
766 output_type: Some(
767 self.lifter.spv_ptr_type(addr_space, layout.original_type),
768 ),
769 }),
770 inputs: access_chain_inputs,
771 }
772 }
773 DataInstKind::QPtr(op @ (QPtrOp::Load | QPtrOp::Store)) => {
774 let (spv_opcode, access_type) = match op {
775 QPtrOp::Load => (wk.OpLoad, data_inst_form_def.output_type.unwrap()),
776 QPtrOp::Store => (wk.OpStore, type_of_val(data_inst_def.inputs[1])),
777 _ => unreachable!(),
778 };
779
780 let maybe_ajustment = {
782 let input_idx = 0;
783 let ptr = data_inst_def.inputs[input_idx];
784 let (addr_space, pointee_layout) = type_of_val_as_spv_ptr_with_layout(ptr)?;
785 self.maybe_adjust_pointer_for_access(
786 ptr,
787 addr_space,
788 pointee_layout,
789 access_type,
790 )?
791 .map(|access_chain_data_inst_def| (input_idx, access_chain_data_inst_def))
792 .into_iter()
793 };
794
795 let mut new_data_inst_def = DataInstDef {
796 form: cx.intern(DataInstFormDef {
797 kind: DataInstKind::SpvInst(spv_opcode.into()),
798 output_type: data_inst_form_def.output_type,
799 }),
800 ..data_inst_def.clone()
801 };
802
803 for (input_idx, mut access_chain_data_inst_def) in maybe_ajustment {
805 self.resolve_deferred_ptr_noop_uses(&mut access_chain_data_inst_def.inputs);
807 self.add_value_uses(&access_chain_data_inst_def.inputs);
808
809 let access_chain_data_inst = func_at_data_inst
810 .reborrow()
811 .data_insts
812 .define(cx, access_chain_data_inst_def.into());
813
814 let data_inst = func_at_data_inst.position;
821 let func = func_at_data_inst.reborrow().at(());
822 match &mut func.control_nodes[parent_block].kind {
823 ControlNodeKind::Block { insts } => {
824 insts.insert_before(access_chain_data_inst, data_inst, func.data_insts);
825 }
826 _ => unreachable!(),
827 }
828
829 new_data_inst_def.inputs[input_idx] =
830 Value::DataInstOutput(access_chain_data_inst);
831 }
832
833 new_data_inst_def
834 }
835
836 DataInstKind::SpvInst(_) | DataInstKind::SpvExtInst { .. } => {
837 let mut to_spv_ptr_input_adjustments = vec![];
838 let mut from_spv_ptr_output = None;
839 for attr in &cx[data_inst_def.attrs].attrs {
840 match *attr {
841 Attr::QPtr(QPtrAttr::ToSpvPtrInput {
842 input_idx,
843 pointee: expected_pointee_type,
844 }) => {
845 let input_idx = usize::try_from(input_idx).unwrap();
846 let expected_pointee_type = expected_pointee_type.0;
847
848 let input_ptr = data_inst_def.inputs[input_idx];
849 let (input_ptr_addr_space, input_pointee_layout) =
850 type_of_val_as_spv_ptr_with_layout(input_ptr)?;
851
852 if let Some(access_chain_data_inst_def) = self
853 .maybe_adjust_pointer_for_access(
854 input_ptr,
855 input_ptr_addr_space,
856 input_pointee_layout,
857 expected_pointee_type,
858 )?
859 {
860 to_spv_ptr_input_adjustments
861 .push((input_idx, access_chain_data_inst_def));
862 }
863 }
864 Attr::QPtr(QPtrAttr::FromSpvPtrOutput { addr_space, pointee }) => {
865 assert!(from_spv_ptr_output.is_none());
866 from_spv_ptr_output = Some((addr_space.0, pointee.0));
867 }
868 _ => {}
869 }
870 }
871
872 if to_spv_ptr_input_adjustments.is_empty() && from_spv_ptr_output.is_none() {
873 return Ok(Transformed::Unchanged);
874 }
875
876 let mut new_data_inst_def = data_inst_def.clone();
877
878 for (input_idx, mut access_chain_data_inst_def) in to_spv_ptr_input_adjustments {
880 self.resolve_deferred_ptr_noop_uses(&mut access_chain_data_inst_def.inputs);
882 self.add_value_uses(&access_chain_data_inst_def.inputs);
883
884 let access_chain_data_inst = func_at_data_inst
885 .reborrow()
886 .data_insts
887 .define(cx, access_chain_data_inst_def.into());
888
889 let data_inst = func_at_data_inst.position;
896 let func = func_at_data_inst.reborrow().at(());
897 match &mut func.control_nodes[parent_block].kind {
898 ControlNodeKind::Block { insts } => {
899 insts.insert_before(access_chain_data_inst, data_inst, func.data_insts);
900 }
901 _ => unreachable!(),
902 }
903
904 new_data_inst_def.inputs[input_idx] =
905 Value::DataInstOutput(access_chain_data_inst);
906 }
907
908 if let Some((addr_space, pointee_type)) = from_spv_ptr_output {
909 new_data_inst_def.form = cx.intern(DataInstFormDef {
910 output_type: Some(self.lifter.spv_ptr_type(addr_space, pointee_type)),
911 ..cx[new_data_inst_def.form].clone()
912 });
913 }
914
915 new_data_inst_def
916 }
917 };
918 Ok(Transformed::Changed(replacement_data_inst_def))
919 }
920
921 fn maybe_adjust_pointer_for_access(
927 &self,
928 ptr: Value,
929 addr_space: AddrSpace,
930 mut pointee_layout: TypeLayout,
931 access_type: Type,
932 ) -> Result<Option<DataInstDef>, LiftError> {
933 let wk = self.lifter.wk;
934
935 let access_layout = self.lifter.layout_of(access_type)?;
936
937 let mut access_chain_inputs: SmallVec<_> = [ptr].into_iter().collect();
940
941 if let TypeLayout::HandleArray(handle, _) = pointee_layout {
942 access_chain_inputs.push(Value::Const(self.lifter.const_u32(0)));
943 pointee_layout = TypeLayout::Handle(handle);
944 }
945 match (pointee_layout, access_layout) {
946 (TypeLayout::HandleArray(..), _) => unreachable!(),
947
948 (_, TypeLayout::Handle(shapes::Handle::Buffer(..))) => {
950 return Err(LiftError(Diag::bug(["cannot access whole Buffer".into()])));
951 }
952 (_, TypeLayout::HandleArray(..)) => {
953 return Err(LiftError(Diag::bug(["cannot access whole HandleArray".into()])));
954 }
955 (_, TypeLayout::Concrete(access_layout))
956 if access_layout.mem_layout.dyn_unit_stride.is_some() =>
957 {
958 return Err(LiftError(Diag::bug(["cannot access unsized type".into()])));
959 }
960 (TypeLayout::Handle(shapes::Handle::Buffer(..)), _) => {
961 return Err(LiftError(Diag::bug(["cannot access into Buffer".into()])));
962 }
963 (TypeLayout::Handle(_), TypeLayout::Concrete(_)) => {
964 return Err(LiftError(Diag::bug(["cannot access Handle as memory".into()])));
965 }
966 (TypeLayout::Concrete(_), TypeLayout::Handle(_)) => {
967 return Err(LiftError(Diag::bug(["cannot access memory as Handle".into()])));
968 }
969
970 (
971 TypeLayout::Handle(shapes::Handle::Opaque(pointee_handle_type)),
972 TypeLayout::Handle(shapes::Handle::Opaque(access_handle_type)),
973 ) => {
974 if pointee_handle_type != access_handle_type {
975 return Err(LiftError(Diag::bug([
976 "(opaque handle) pointer vs access type mismatch".into(),
977 ])));
978 }
979 }
980
981 (TypeLayout::Concrete(mut pointee_layout), TypeLayout::Concrete(access_layout)) => {
982 while pointee_layout.original_type != access_layout.original_type {
984 let idx = {
985 let offset_range = 0..access_layout.mem_layout.fixed_base.size;
986 let mut component_indices =
987 pointee_layout.components.find_components_containing(offset_range);
988 match (component_indices.next(), component_indices.next()) {
989 (None, _) => {
990 return Err(LiftError(Diag::bug([
991 "accessed type not found in pointee type layout".into(),
992 ])));
993 }
994 (Some(_), Some(_)) => {
998 return Err(LiftError(Diag::bug([
999 "ambiguity due to ZSTs in pointee type layout".into(),
1000 ])));
1001 }
1002 (Some(idx), None) => idx,
1003 }
1004 };
1005
1006 let idx_as_i32 = i32::try_from(idx).ok().ok_or_else(|| {
1007 LiftError(Diag::bug([
1008 format!("{idx} not representable as a positive s32").into()
1009 ]))
1010 })?;
1011 access_chain_inputs
1012 .push(Value::Const(self.lifter.const_u32(idx_as_i32 as u32)));
1013
1014 pointee_layout = match &pointee_layout.components {
1015 Components::Scalar => unreachable!(),
1016 Components::Elements { elem, .. } => elem.clone(),
1017 Components::Fields { layouts, .. } => layouts[idx].clone(),
1018 };
1019 }
1020 }
1021 }
1022
1023 Ok(if access_chain_inputs.len() > 1 {
1024 Some(DataInstDef {
1025 attrs: Default::default(),
1026 form: self.lifter.cx.intern(DataInstFormDef {
1027 kind: DataInstKind::SpvInst(wk.OpAccessChain.into()),
1028 output_type: Some(self.lifter.spv_ptr_type(addr_space, access_type)),
1029 }),
1030 inputs: access_chain_inputs,
1031 })
1032 } else {
1033 None
1034 })
1035 }
1036
1037 fn resolve_deferred_ptr_noop_uses(&self, values: &mut [Value]) {
1043 for v in values {
1044 while let Value::DataInstOutput(data_inst) = *v {
1047 match self.deferred_ptr_noops.get(&data_inst) {
1048 Some(ptr_noop) => {
1049 *v = ptr_noop.output_pointer;
1050 }
1051 None => break,
1052 }
1053 }
1054 }
1055 }
1056
1057 fn add_value_uses(&mut self, values: &[Value]) {
1060 for &v in values {
1061 if let Value::DataInstOutput(data_inst) = v {
1062 let count = self.data_inst_use_counts.entry(data_inst);
1063 *count = Some(
1064 NonZeroU32::new(count.map_or(0, |c| c.get()).checked_add(1).unwrap()).unwrap(),
1065 );
1066 }
1067 }
1068 }
1069 fn remove_value_uses(&mut self, values: &[Value]) {
1070 for &v in values {
1071 if let Value::DataInstOutput(data_inst) = v {
1072 let count = self.data_inst_use_counts.entry(data_inst);
1073 *count = NonZeroU32::new(count.unwrap().get() - 1);
1074 }
1075 }
1076 }
1077}
1078
1079impl Transformer for LiftToSpvPtrInstsInFunc<'_> {
1080 fn transform_const_use(&mut self, ct: Const) -> Transformed<Const> {
1083 let ct_def = &self.lifter.cx[ct];
1085 if let ConstKind::PtrToGlobalVar(gv) = ct_def.kind {
1086 Transformed::Changed(self.lifter.cx.intern(ConstDef {
1087 attrs: ct_def.attrs,
1088 ty: self.global_vars[gv].type_of_ptr_to,
1089 kind: ct_def.kind.clone(),
1090 }))
1091 } else {
1092 Transformed::Unchanged
1093 }
1094 }
1095
1096 fn transform_value_use(&mut self, v: &Value) -> Transformed<Value> {
1097 self.add_value_uses(&[*v]);
1098
1099 v.inner_transform_with(self)
1100 }
1101
1102 fn in_place_transform_control_node_def(
1111 &mut self,
1112 mut func_at_control_node: FuncAtMut<'_, ControlNode>,
1113 ) {
1114 func_at_control_node.reborrow().inner_in_place_transform_with(self);
1115
1116 let control_node = func_at_control_node.position;
1117 if let ControlNodeKind::Block { insts } = func_at_control_node.reborrow().def().kind {
1118 let mut func_at_inst_iter = func_at_control_node.reborrow().at(insts).into_iter();
1119 while let Some(mut func_at_inst) = func_at_inst_iter.next() {
1120 let mut lifted = self.try_lift_data_inst_def(func_at_inst.reborrow(), control_node);
1121 if let Ok(Transformed::Unchanged) = lifted {
1122 let data_inst_def = func_at_inst.reborrow().def();
1123 let data_inst_form_def = &self.lifter.cx[data_inst_def.form];
1124 if let DataInstKind::QPtr(_) = data_inst_form_def.kind {
1125 lifted =
1126 Err(LiftError(Diag::bug(["unimplemented qptr instruction".into()])));
1127 } else if let Some(ty) = data_inst_form_def.output_type {
1128 if matches!(self.lifter.cx[ty].kind, TypeKind::QPtr) {
1129 lifted = Err(LiftError(Diag::bug([
1130 "unimplemented qptr-producing instruction".into(),
1131 ])));
1132 }
1133 }
1134 }
1135 match lifted {
1136 Ok(Transformed::Unchanged) => {}
1137 Ok(Transformed::Changed(new_def)) => {
1138 let data_inst_def = func_at_inst.def();
1141 self.remove_value_uses(&data_inst_def.inputs);
1142 *data_inst_def = new_def;
1143 self.resolve_deferred_ptr_noop_uses(&mut data_inst_def.inputs);
1144 self.add_value_uses(&data_inst_def.inputs);
1145 }
1146 Err(LiftError(e)) => {
1147 let data_inst_def = func_at_inst.def();
1148
1149 self.func_has_qptr_analysis_bug_diags = self
1151 .func_has_qptr_analysis_bug_diags
1152 || self.lifter.cx[data_inst_def.attrs].attrs.iter().any(|attr| {
1153 match attr {
1154 Attr::Diagnostics(diags) => {
1155 diags.0.iter().any(|diag| match diag.level {
1156 DiagLevel::Bug(loc) => {
1157 loc.file().ends_with("qptr/analyze.rs")
1158 || loc.file().ends_with("qptr\\analyze.rs")
1159 }
1160 _ => false,
1161 })
1162 }
1163 _ => false,
1164 }
1165 });
1166
1167 if !self.func_has_qptr_analysis_bug_diags {
1168 data_inst_def.attrs.push_diag(&self.lifter.cx, e);
1169 }
1170 }
1171 }
1172 }
1173 }
1174 }
1175
1176 fn in_place_transform_func_decl(&mut self, func_decl: &mut FuncDecl) {
1177 func_decl.inner_in_place_transform_with(self);
1178
1179 if let DeclDef::Present(func_def_body) = &mut func_decl.def {
1181 let deferred_ptr_noops = mem::take(&mut self.deferred_ptr_noops);
1182 for (inst, ptr_noop) in deferred_ptr_noops.into_iter().rev() {
1185 if self.data_inst_use_counts.get(inst).is_none() {
1186 match &mut func_def_body.control_nodes[ptr_noop.parent_block].kind {
1193 ControlNodeKind::Block { insts } => {
1194 insts.remove(inst, &mut func_def_body.data_insts);
1195 }
1196 _ => unreachable!(),
1197 }
1198
1199 self.remove_value_uses(&func_def_body.at(inst).def().inputs);
1200 }
1201 }
1202 }
1203 }
1204}