spirt/qptr/
analyze.rs

1//! [`QPtr`](crate::TypeKind::QPtr) usage analysis (for legalizing/lifting).
2
3// HACK(eddyb) sharing layout code with other modules.
4use super::{QPtrMemUsageKind, layout::*};
5
6use super::{QPtrAttr, QPtrMemUsage, QPtrOp, QPtrUsage, shapes};
7use crate::func_at::FuncAt;
8use crate::visit::{InnerVisit, Visitor};
9use crate::{
10    AddrSpace, Attr, AttrSet, AttrSetDef, Const, ConstKind, Context, ControlNode, ControlNodeKind,
11    DataInst, DataInstForm, DataInstKind, DeclDef, Diag, EntityList, ExportKey, Exportee, Func,
12    FxIndexMap, GlobalVar, Module, OrdAssertEq, Type, TypeKind, Value,
13};
14use itertools::Either;
15use rustc_hash::FxHashMap;
16use smallvec::SmallVec;
17use std::mem;
18use std::num::NonZeroU32;
19use std::ops::Bound;
20use std::rc::Rc;
21
22#[derive(Clone)]
23struct AnalysisError(Diag);
24
25struct UsageMerger<'a> {
26    layout_cache: &'a LayoutCache<'a>,
27}
28
29/// Result type for `UsageMerger` methods - unlike `Result<T, AnalysisError>`,
30/// this always keeps the `T` value, even in the case of an error.
31struct MergeResult<T> {
32    merged: T,
33    error: Option<AnalysisError>,
34}
35
36impl<T> MergeResult<T> {
37    fn ok(merged: T) -> Self {
38        Self { merged, error: None }
39    }
40
41    fn into_result(self) -> Result<T, AnalysisError> {
42        let Self { merged, error } = self;
43        match error {
44            None => Ok(merged),
45            Some(e) => Err(e),
46        }
47    }
48
49    fn map<U>(self, f: impl FnOnce(T) -> U) -> MergeResult<U> {
50        let Self { merged, error } = self;
51        let merged = f(merged);
52        MergeResult { merged, error }
53    }
54}
55
56impl UsageMerger<'_> {
57    fn merge(&self, a: QPtrUsage, b: QPtrUsage) -> MergeResult<QPtrUsage> {
58        match (a, b) {
59            (
60                QPtrUsage::Handles(shapes::Handle::Opaque(a)),
61                QPtrUsage::Handles(shapes::Handle::Opaque(b)),
62            ) if a == b => MergeResult::ok(QPtrUsage::Handles(shapes::Handle::Opaque(a))),
63
64            (
65                QPtrUsage::Handles(shapes::Handle::Buffer(a_as, a)),
66                QPtrUsage::Handles(shapes::Handle::Buffer(b_as, b)),
67            ) => {
68                // HACK(eddyb) the `AddrSpace` field is entirely redundant.
69                assert!(a_as == AddrSpace::Handles && b_as == AddrSpace::Handles);
70
71                self.merge_mem(a, b).map(|usage| {
72                    QPtrUsage::Handles(shapes::Handle::Buffer(AddrSpace::Handles, usage))
73                })
74            }
75
76            (QPtrUsage::Memory(a), QPtrUsage::Memory(b)) => {
77                self.merge_mem(a, b).map(QPtrUsage::Memory)
78            }
79
80            (a, b) => {
81                MergeResult {
82                    // FIXME(eddyb) there may be a better choice here, but it
83                    // generally doesn't matter, as this method only has one
84                    // caller, and it just calls `.into_result()` right away.
85                    merged: a.clone(),
86                    error: Some(AnalysisError(Diag::bug([
87                        "merge: ".into(),
88                        a.into(),
89                        " vs ".into(),
90                        b.into(),
91                    ]))),
92                }
93            }
94        }
95    }
96
97    fn merge_mem(&self, a: QPtrMemUsage, b: QPtrMemUsage) -> MergeResult<QPtrMemUsage> {
98        // NOTE(eddyb) this is possible because it's currently impossible for
99        // the merged usage to be outside the bounds of *both* `a` and `b`.
100        let max_size = match (a.max_size, b.max_size) {
101            (Some(a), Some(b)) => Some(a.max(b)),
102            (None, _) | (_, None) => None,
103        };
104
105        // Ensure that `a` is "larger" than `b`, or at least the same size
106        // (when either they're identical, or one is a "newtype" of the other),
107        // to make it easier to handle all the possible interactions below,
108        // by skipping (or deprioritizing, if supported) the "wrong direction".
109        let mut sorted = [a, b];
110        sorted.sort_by_key(|usage| {
111            #[derive(PartialEq, Eq, PartialOrd, Ord)]
112            enum MaxSize<T> {
113                Fixed(T),
114                // FIXME(eddyb) this probably needs to track "min size"?
115                Dynamic,
116            }
117            let max_size = usage.max_size.map_or(MaxSize::Dynamic, MaxSize::Fixed);
118
119            // When sizes are equal, pick the more restrictive side.
120            #[derive(PartialEq, Eq, PartialOrd, Ord)]
121            enum TypeStrictness {
122                Any,
123                Array,
124                Exact,
125            }
126            #[allow(clippy::match_same_arms)]
127            let type_strictness = match usage.kind {
128                QPtrMemUsageKind::Unused | QPtrMemUsageKind::OffsetBase(_) => TypeStrictness::Any,
129
130                QPtrMemUsageKind::DynOffsetBase { .. } => TypeStrictness::Array,
131
132                // FIXME(eddyb) this should be `Any`, even if in theory it
133                // could contain arrays or structs that need decomposition
134                // (note that, for typed reads/write, arrays do not need to be
135                // *indexed* to work, i.e. they *do not* require `DynOffset`s,
136                // `Offset`s suffice, and for them `DynOffsetBase` is at most
137                // a "run-length"/deduplication optimization over `OffsetBase`).
138                // NOTE(eddyb) this should still prefer `OpTypeVector` over `DynOffsetBase`!
139                QPtrMemUsageKind::DirectAccess(_) => TypeStrictness::Exact,
140
141                QPtrMemUsageKind::StrictlyTyped(_) => TypeStrictness::Exact,
142            };
143
144            (max_size, type_strictness)
145        });
146        let [b, a] = sorted;
147        assert_eq!(max_size, a.max_size);
148
149        self.merge_mem_at(a, 0, b)
150    }
151
152    // FIXME(eddyb) make the name of this clarify the asymmetric effect, something
153    // like "make `a` compatible with `offset => b`".
154    fn merge_mem_at(
155        &self,
156        a: QPtrMemUsage,
157        b_offset_in_a: u32,
158        b: QPtrMemUsage,
159    ) -> MergeResult<QPtrMemUsage> {
160        // NOTE(eddyb) this is possible because it's currently impossible for
161        // the merged usage to be outside the bounds of *both* `a` and `b`.
162        let max_size = match (a.max_size, b.max_size) {
163            (Some(a), Some(b)) => Some(a.max(b.checked_add(b_offset_in_a).unwrap())),
164            (None, _) | (_, None) => None,
165        };
166
167        // HACK(eddyb) we require biased `a` vs `b` (see `merge_mem` method above).
168        assert_eq!(max_size, a.max_size);
169
170        // Decompose the "smaller" and/or "less strict" side (`b`) first.
171        match b.kind {
172            // `Unused`s are always ignored.
173            QPtrMemUsageKind::Unused => return MergeResult::ok(a),
174
175            QPtrMemUsageKind::OffsetBase(b_entries)
176                if {
177                    // HACK(eddyb) this check was added later, after it turned out
178                    // that *deep* flattening of arbitrary offsets in `b` would've
179                    // required constant-folding of `qptr.offset` in `qptr::lift`,
180                    // to not need all the type nesting levels for `OpAccessChain`.
181                    b_offset_in_a == 0
182                } =>
183            {
184                // FIXME(eddyb) this whole dance only needed due to `Rc`.
185                let b_entries = Rc::try_unwrap(b_entries);
186                let b_entries = match b_entries {
187                    Ok(entries) => Either::Left(entries.into_iter()),
188                    Err(ref entries) => Either::Right(entries.iter().map(|(&k, v)| (k, v.clone()))),
189                };
190
191                let mut ab = a;
192                let mut all_errors = None;
193                for (b_offset, b_sub_usage) in b_entries {
194                    let MergeResult { merged, error: new_error } = self.merge_mem_at(
195                        ab,
196                        b_offset.checked_add(b_offset_in_a).unwrap(),
197                        b_sub_usage,
198                    );
199                    ab = merged;
200
201                    // FIXME(eddyb) move some of this into `MergeResult`!
202                    if let Some(AnalysisError(e)) = new_error {
203                        let all_errors =
204                            &mut all_errors.get_or_insert(AnalysisError(Diag::bug([]))).0.message;
205                        // FIXME(eddyb) should this mean `MergeResult` should
206                        // use `errors: Vec<AnalysisError>` instead of `Option`?
207                        if !all_errors.is_empty() {
208                            all_errors.push("\n".into());
209                        }
210                        // FIXME(eddyb) this is scuffed because the error might
211                        // (or really *should*) already refer to the right offset!
212                        all_errors.push(format!("+{b_offset} => ").into());
213                        all_errors.extend(e.message);
214                    }
215                }
216                return MergeResult {
217                    merged: ab,
218                    // FIXME(eddyb) should this mean `MergeResult` should
219                    // use `errors: Vec<AnalysisError>` instead of `Option`?
220                    error: all_errors.map(|AnalysisError(mut e)| {
221                        e.message.insert(0, "merge_mem: conflicts:\n".into());
222                        AnalysisError(e)
223                    }),
224                };
225            }
226
227            _ => {}
228        }
229
230        let kind = match a.kind {
231            // `Unused`s are always ignored.
232            QPtrMemUsageKind::Unused => MergeResult::ok(b.kind),
233
234            // Typed leaves must support any possible usage applied to them
235            // (when they match, or overtake, that usage, in size, like here),
236            // with their inherent hierarchy (i.e. their array/struct nesting).
237            QPtrMemUsageKind::StrictlyTyped(a_type) | QPtrMemUsageKind::DirectAccess(a_type) => {
238                let b_type_at_offset_0 = match b.kind {
239                    QPtrMemUsageKind::StrictlyTyped(b_type)
240                    | QPtrMemUsageKind::DirectAccess(b_type)
241                        if b_offset_in_a == 0 =>
242                    {
243                        Some(b_type)
244                    }
245                    _ => None,
246                };
247                let ty = if Some(a_type) == b_type_at_offset_0 {
248                    MergeResult::ok(a_type)
249                } else {
250                    // Returns `Some(MergeResult::ok(ty))` iff `usage` is valid
251                    // for type `ty`, and `None` iff invalid w/o layout errors
252                    // (see `mem_layout_supports_usage_at_offset` for more details).
253                    let type_supporting_usage_at_offset = |ty, usage_offset, usage| {
254                        let supports_usage = match self.layout_of(ty) {
255                            // FIXME(eddyb) should this be `unreachable!()`? also, is
256                            // it possible to end up with `ty` being an `OpTypeStruct`
257                            // decorated with `Block`, showing up as a `Buffer` handle?
258                            //
259                            // NOTE(eddyb) `Block`-annotated buffer types are *not*
260                            // usable anywhere inside buffer data, since they would
261                            // conflict with our own `Block`-annotated wrapper.
262                            Ok(TypeLayout::Handle(_) | TypeLayout::HandleArray(..)) => {
263                                Err(AnalysisError(Diag::bug([
264                                    "merge_mem: impossible handle type for QPtrMemUsage".into(),
265                                ])))
266                            }
267                            Ok(TypeLayout::Concrete(concrete)) => {
268                                Ok(concrete.supports_usage_at_offset(usage_offset, usage))
269                            }
270
271                            Err(e) => Err(e),
272                        };
273                        match supports_usage {
274                            Ok(false) => None,
275                            Ok(true) | Err(_) => {
276                                Some(MergeResult { merged: ty, error: supports_usage.err() })
277                            }
278                        }
279                    };
280
281                    type_supporting_usage_at_offset(a_type, b_offset_in_a, &b)
282                        .or_else(|| {
283                            b_type_at_offset_0.and_then(|b_type_at_offset_0| {
284                                type_supporting_usage_at_offset(b_type_at_offset_0, 0, &a)
285                            })
286                        })
287                        .unwrap_or_else(|| {
288                            MergeResult {
289                                merged: a_type,
290                                // FIXME(eddyb) this should ideally embed the types in the
291                                // error somehow.
292                                error: Some(AnalysisError(Diag::bug([
293                                    "merge_mem: type subcomponents incompatible with usage ("
294                                        .into(),
295                                    QPtrUsage::Memory(a.clone()).into(),
296                                    " vs ".into(),
297                                    QPtrUsage::Memory(b.clone()).into(),
298                                    ")".into(),
299                                ]))),
300                            }
301                        })
302                };
303
304                // FIXME(eddyb) if the chosen (maybe-larger) side isn't strict,
305                // it should also be possible to expand it into its components,
306                // with the other (maybe-smaller) side becoming a leaf.
307
308                // FIXME(eddyb) this might not enough because the
309                // strict leaf could be *nested* inside `b`!!!
310                let is_strict = |kind| matches!(kind, &QPtrMemUsageKind::StrictlyTyped(_));
311                if is_strict(&a.kind) || is_strict(&b.kind) {
312                    ty.map(QPtrMemUsageKind::StrictlyTyped)
313                } else {
314                    ty.map(QPtrMemUsageKind::DirectAccess)
315                }
316            }
317
318            QPtrMemUsageKind::DynOffsetBase { element: mut a_element, stride: a_stride } => {
319                let b_offset_in_a_element = b_offset_in_a % a_stride;
320
321                // Array-like dynamic offsetting needs to always merge any usage that
322                // fits inside the stride, with its "element" usage, no matter how
323                // complex it may be (notably, this is needed for nested arrays).
324                if b.max_size
325                    .and_then(|b_max_size| b_max_size.checked_add(b_offset_in_a_element))
326                    .map_or(false, |b_in_a_max_size| b_in_a_max_size <= a_stride.get())
327                {
328                    // FIXME(eddyb) this in-place merging dance only needed due to `Rc`.
329                    ({
330                        let a_element_mut = Rc::make_mut(&mut a_element);
331                        let a_element = mem::replace(a_element_mut, QPtrMemUsage::UNUSED);
332                        // FIXME(eddyb) remove this silliness by making `merge_mem_at` do symmetrical sorting.
333                        if b_offset_in_a_element == 0 {
334                            self.merge_mem(a_element, b)
335                        } else {
336                            self.merge_mem_at(a_element, b_offset_in_a_element, b)
337                        }
338                        .map(|merged| *a_element_mut = merged)
339                    })
340                    .map(|()| QPtrMemUsageKind::DynOffsetBase {
341                        element: a_element,
342                        stride: a_stride,
343                    })
344                } else {
345                    match b.kind {
346                        QPtrMemUsageKind::DynOffsetBase {
347                            element: b_element,
348                            stride: b_stride,
349                        } if b_offset_in_a_element == 0 && a_stride == b_stride => {
350                            // FIXME(eddyb) this in-place merging dance only needed due to `Rc`.
351                            ({
352                                let a_element_mut = Rc::make_mut(&mut a_element);
353                                let a_element = mem::replace(a_element_mut, QPtrMemUsage::UNUSED);
354                                let b_element =
355                                    Rc::try_unwrap(b_element).unwrap_or_else(|e| (*e).clone());
356                                self.merge_mem(a_element, b_element)
357                                    .map(|merged| *a_element_mut = merged)
358                            })
359                            .map(|()| {
360                                QPtrMemUsageKind::DynOffsetBase {
361                                    element: a_element,
362                                    stride: a_stride,
363                                }
364                            })
365                        }
366                        _ => {
367                            // FIXME(eddyb) implement somehow (by adjusting stride?).
368                            // NOTE(eddyb) with `b` as an `DynOffsetBase`/`OffsetBase`, it could
369                            // also be possible to superimpose its offset patterns onto `a`,
370                            // though that's easier for `OffsetBase` than `DynOffsetBase`.
371                            // HACK(eddyb) needed due to `a` being moved out of.
372                            let a = QPtrMemUsage {
373                                max_size: a.max_size,
374                                kind: QPtrMemUsageKind::DynOffsetBase {
375                                    element: a_element,
376                                    stride: a_stride,
377                                },
378                            };
379                            MergeResult {
380                                merged: a.kind.clone(),
381                                error: Some(AnalysisError(Diag::bug([
382                                    format!("merge_mem: unimplemented non-intra-element merging into stride={a_stride} (")
383                                        .into(),
384                                    QPtrUsage::Memory(a).into(),
385                                    " vs ".into(),
386                                    QPtrUsage::Memory(b).into(),
387                                    ")".into(),
388                                ]))),
389                            }
390                        }
391                    }
392                }
393            }
394
395            QPtrMemUsageKind::OffsetBase(mut a_entries) => {
396                let overlapping_entries = a_entries
397                    .range((
398                        Bound::Unbounded,
399                        b.max_size.map_or(Bound::Unbounded, |b_max_size| {
400                            Bound::Excluded(b_offset_in_a.checked_add(b_max_size).unwrap())
401                        }),
402                    ))
403                    .rev()
404                    .take_while(|(a_sub_offset, a_sub_usage)| {
405                        a_sub_usage.max_size.map_or(true, |a_sub_max_size| {
406                            a_sub_offset.checked_add(a_sub_max_size).unwrap() > b_offset_in_a
407                        })
408                    });
409
410                // FIXME(eddyb) this is a bit inefficient but we don't have
411                // cursors, so we have to buffer the `BTreeMap` keys here.
412                let overlapping_offsets: SmallVec<[u32; 16]> =
413                    overlapping_entries.map(|(&a_sub_offset, _)| a_sub_offset).collect();
414                let a_entries_mut = Rc::make_mut(&mut a_entries);
415                let mut all_errors = None;
416                let (mut b_offset_in_a, mut b) = (b_offset_in_a, b);
417                for a_sub_offset in overlapping_offsets {
418                    let a_sub_usage = a_entries_mut.remove(&a_sub_offset).unwrap();
419
420                    // HACK(eddyb) this replicates the condition in which
421                    // `merge_mem_at` would fail its similar assert, some of
422                    // the cases denied here might be legal, but they're rare
423                    // enough that we can do this for now.
424                    let is_illegal = a_sub_offset != b_offset_in_a && {
425                        let (a_sub_total_max_size, b_total_max_size) = (
426                            a_sub_usage.max_size.map(|a| a.checked_add(a_sub_offset).unwrap()),
427                            b.max_size.map(|b| b.checked_add(b_offset_in_a).unwrap()),
428                        );
429                        let total_max_size_merged = match (a_sub_total_max_size, b_total_max_size) {
430                            (Some(a), Some(b)) => Some(a.max(b)),
431                            (None, _) | (_, None) => None,
432                        };
433                        total_max_size_merged
434                            != if a_sub_offset < b_offset_in_a {
435                                a_sub_total_max_size
436                            } else {
437                                b_total_max_size
438                            }
439                    };
440                    if is_illegal {
441                        // HACK(eddyb) needed due to `a` being moved out of.
442                        let a = QPtrMemUsage {
443                            max_size: a.max_size,
444                            kind: QPtrMemUsageKind::OffsetBase(a_entries.clone()),
445                        };
446                        return MergeResult {
447                            merged: QPtrMemUsage {
448                                max_size,
449                                kind: QPtrMemUsageKind::OffsetBase(a_entries),
450                            },
451                            error: Some(AnalysisError(Diag::bug([
452                                format!(
453                                    "merge_mem: unsupported straddling overlap \
454                                     at offsets {a_sub_offset} vs {b_offset_in_a} ("
455                                )
456                                .into(),
457                                QPtrUsage::Memory(a).into(),
458                                " vs ".into(),
459                                QPtrUsage::Memory(b).into(),
460                                ")".into(),
461                            ]))),
462                        };
463                    }
464
465                    let new_error;
466                    (b_offset_in_a, MergeResult { merged: b, error: new_error }) =
467                        if a_sub_offset < b_offset_in_a {
468                            (
469                                a_sub_offset,
470                                self.merge_mem_at(a_sub_usage, b_offset_in_a - a_sub_offset, b),
471                            )
472                        } else {
473                            // FIXME(eddyb) remove this silliness by making `merge_mem_at` do symmetrical sorting.
474                            if a_sub_offset - b_offset_in_a == 0 {
475                                (b_offset_in_a, self.merge_mem(b, a_sub_usage))
476                            } else {
477                                (
478                                    b_offset_in_a,
479                                    self.merge_mem_at(b, a_sub_offset - b_offset_in_a, a_sub_usage),
480                                )
481                            }
482                        };
483
484                    // FIXME(eddyb) move some of this into `MergeResult`!
485                    if let Some(AnalysisError(e)) = new_error {
486                        let all_errors =
487                            &mut all_errors.get_or_insert(AnalysisError(Diag::bug([]))).0.message;
488                        // FIXME(eddyb) should this mean `MergeResult` should
489                        // use `errors: Vec<AnalysisError>` instead of `Option`?
490                        if !all_errors.is_empty() {
491                            all_errors.push("\n".into());
492                        }
493                        // FIXME(eddyb) this is scuffed because the error might
494                        // (or really *should*) already refer to the right offset!
495                        all_errors.push(format!("+{a_sub_offset} => ").into());
496                        all_errors.extend(e.message);
497                    }
498                }
499                a_entries_mut.insert(b_offset_in_a, b);
500                MergeResult {
501                    merged: QPtrMemUsageKind::OffsetBase(a_entries),
502                    // FIXME(eddyb) should this mean `MergeResult` should
503                    // use `errors: Vec<AnalysisError>` instead of `Option`?
504                    error: all_errors.map(|AnalysisError(mut e)| {
505                        e.message.insert(0, "merge_mem: conflicts:\n".into());
506                        AnalysisError(e)
507                    }),
508                }
509            }
510        };
511        kind.map(|kind| QPtrMemUsage { max_size, kind })
512    }
513
514    /// Attempt to compute a `TypeLayout` for a given (SPIR-V) `Type`.
515    fn layout_of(&self, ty: Type) -> Result<TypeLayout, AnalysisError> {
516        self.layout_cache.layout_of(ty).map_err(|LayoutError(err)| AnalysisError(err))
517    }
518}
519
520impl MemTypeLayout {
521    /// Determine if this layout is compatible with `usage` at `usage_offset`.
522    ///
523    /// That is, all typed leaves of `usage` must be found inside `self`, at
524    /// their respective offsets, and all [`QPtrMemUsageKind::DynOffsetBase`]s
525    /// must find a same-stride array inside `self` (to allow dynamic indexing).
526    //
527    // FIXME(eddyb) consider using `Result` to make it unambiguous.
528    fn supports_usage_at_offset(&self, usage_offset: u32, usage: &QPtrMemUsage) -> bool {
529        if let QPtrMemUsageKind::Unused = usage.kind {
530            return true;
531        }
532
533        // "Fast accept" based on type alone (expected as recursion base case).
534        if let QPtrMemUsageKind::StrictlyTyped(usage_type)
535        | QPtrMemUsageKind::DirectAccess(usage_type) = usage.kind
536        {
537            if usage_offset == 0 && self.original_type == usage_type {
538                return true;
539            }
540        }
541
542        {
543            // FIXME(eddyb) should `QPtrMemUsage` track a `min_size` as well?
544            // FIXME(eddyb) duplicated below.
545            let min_usage_offset_range =
546                usage_offset..usage_offset.saturating_add(usage.max_size.unwrap_or(0));
547
548            // "Fast reject" based on size alone (expected w/ multiple attempts).
549            if self.mem_layout.dyn_unit_stride.is_none()
550                && (self.mem_layout.fixed_base.size < min_usage_offset_range.end
551                    || usage.max_size.is_none())
552            {
553                return false;
554            }
555        }
556
557        let any_component_supports = |usage_offset: u32, usage: &QPtrMemUsage| {
558            // FIXME(eddyb) should `QPtrMemUsage` track a `min_size` as well?
559            // FIXME(eddyb) duplicated above.
560            let min_usage_offset_range =
561                usage_offset..usage_offset.saturating_add(usage.max_size.unwrap_or(0));
562
563            // FIXME(eddyb) `find_components_containing` is linear today but
564            // could be made logarithmic (via binary search).
565            self.components.find_components_containing(min_usage_offset_range).any(
566                |idx| match &self.components {
567                    Components::Scalar => unreachable!(),
568                    Components::Elements { stride, elem, .. } => {
569                        elem.supports_usage_at_offset(usage_offset % stride.get(), usage)
570                    }
571                    Components::Fields { offsets, layouts, .. } => {
572                        layouts[idx].supports_usage_at_offset(usage_offset - offsets[idx], usage)
573                    }
574                },
575            )
576        };
577        match &usage.kind {
578            _ if any_component_supports(usage_offset, usage) => true,
579
580            QPtrMemUsageKind::Unused => unreachable!(),
581
582            QPtrMemUsageKind::StrictlyTyped(_) | QPtrMemUsageKind::DirectAccess(_) => false,
583
584            QPtrMemUsageKind::OffsetBase(entries) => {
585                entries.iter().all(|(&sub_offset, sub_usage)| {
586                    // FIXME(eddyb) maybe this overflow should be propagated up,
587                    // as a sign that `usage` is malformed?
588                    usage_offset.checked_add(sub_offset).map_or(false, |combined_offset| {
589                        // NOTE(eddyb) the reason this is only applicable to
590                        // offset `0` is that *in all other cases*, every
591                        // individual `OffsetBase` requires its own type, to
592                        // allow performing offsets *in steps* (even if the
593                        // offsets could easily be constant-folded, they'd
594                        // *have to* be constant-folded *before* analysis,
595                        // to ensure there is no need for the intermediaries).
596                        if combined_offset == 0 {
597                            self.supports_usage_at_offset(0, sub_usage)
598                        } else {
599                            any_component_supports(combined_offset, sub_usage)
600                        }
601                    })
602                })
603            }
604
605            // Finding an array entirely nested in a component was handled above,
606            // so here `layout` can only be a matching array (same stride and length).
607            QPtrMemUsageKind::DynOffsetBase { element: usage_elem, stride: usage_stride } => {
608                let usage_fixed_len = usage
609                    .max_size
610                    .map(|size| {
611                        if size % usage_stride.get() != 0 {
612                            // FIXME(eddyb) maybe this should be propagated up,
613                            // as a sign that `usage` is malformed?
614                            return Err(());
615                        }
616                        NonZeroU32::new(size / usage_stride.get()).ok_or(())
617                    })
618                    .transpose();
619
620                match &self.components {
621                    // Dynamic offsetting into non-arrays is not supported, and it'd
622                    // only make sense for legalization (or small-length arrays where
623                    // selecting elements based on the index may be a practical choice).
624                    Components::Scalar | Components::Fields { .. } => false,
625
626                    Components::Elements {
627                        stride: layout_stride,
628                        elem: layout_elem,
629                        fixed_len: layout_fixed_len,
630                    } => {
631                        // HACK(eddyb) extend the max length implied by `usage`,
632                        // such that the array can start at offset `0`.
633                        let ext_usage_offset = usage_offset % usage_stride.get();
634                        let ext_usage_fixed_len = usage_fixed_len.and_then(|usage_fixed_len| {
635                            usage_fixed_len
636                                .map(|usage_fixed_len| {
637                                    NonZeroU32::new(
638                                        // FIXME(eddyb) maybe this overflow should be propagated up,
639                                        // as a sign that `usage` is malformed?
640                                        (usage_offset / usage_stride.get())
641                                            .checked_add(usage_fixed_len.get())
642                                            .ok_or(())?,
643                                    )
644                                    .ok_or(())
645                                })
646                                .transpose()
647                        });
648
649                        // FIXME(eddyb) this could maybe be allowed if there is still
650                        // some kind of divisibility relation between the strides.
651                        if ext_usage_offset != 0 {
652                            return false;
653                        }
654
655                        layout_stride == usage_stride
656                            && Ok(*layout_fixed_len) == ext_usage_fixed_len
657                            && layout_elem.supports_usage_at_offset(0, usage_elem)
658                    }
659                }
660            }
661        }
662    }
663}
664
665struct FuncInferUsageResults {
666    param_usages: SmallVec<[Option<Result<QPtrUsage, AnalysisError>>; 2]>,
667    usage_or_err_attrs_to_attach: Vec<(Value, Result<QPtrUsage, AnalysisError>)>,
668}
669
670#[derive(Clone)]
671enum FuncInferUsageState {
672    InProgress,
673    Complete(Rc<FuncInferUsageResults>),
674}
675
676pub struct InferUsage<'a> {
677    cx: Rc<Context>,
678    layout_cache: LayoutCache<'a>,
679
680    global_var_usages: FxIndexMap<GlobalVar, Option<Result<QPtrUsage, AnalysisError>>>,
681    func_states: FxIndexMap<Func, FuncInferUsageState>,
682}
683
684impl<'a> InferUsage<'a> {
685    pub fn new(cx: Rc<Context>, layout_config: &'a LayoutConfig) -> Self {
686        Self {
687            cx: cx.clone(),
688            layout_cache: LayoutCache::new(cx, layout_config),
689
690            global_var_usages: Default::default(),
691            func_states: Default::default(),
692        }
693    }
694
695    pub fn infer_usage_in_module(mut self, module: &mut Module) {
696        for (export_key, &exportee) in &module.exports {
697            if let Exportee::Func(func) = exportee {
698                self.infer_usage_in_func(module, func);
699            }
700
701            // Ensure even unused interface variables get their `qptr.usage`.
702            match export_key {
703                ExportKey::LinkName(_) => {}
704                ExportKey::SpvEntryPoint { imms: _, interface_global_vars } => {
705                    for &gv in interface_global_vars {
706                        self.global_var_usages.entry(gv).or_insert_with(|| {
707                            Some(Ok(match module.global_vars[gv].shape {
708                                Some(shapes::GlobalVarShape::Handles { handle, .. }) => {
709                                    QPtrUsage::Handles(match handle {
710                                        shapes::Handle::Opaque(ty) => shapes::Handle::Opaque(ty),
711                                        shapes::Handle::Buffer(..) => shapes::Handle::Buffer(
712                                            AddrSpace::Handles,
713                                            QPtrMemUsage::UNUSED,
714                                        ),
715                                    })
716                                }
717                                _ => QPtrUsage::Memory(QPtrMemUsage::UNUSED),
718                            }))
719                        });
720                    }
721                }
722            }
723        }
724
725        // Analysis over, write all attributes back to the module.
726        for (gv, usage) in self.global_var_usages {
727            if let Some(usage) = usage {
728                let global_var_def = &mut module.global_vars[gv];
729                match usage {
730                    Ok(usage) => {
731                        // FIXME(eddyb) deduplicate attribute manipulation.
732                        global_var_def.attrs = self.cx.intern(AttrSetDef {
733                            attrs: self.cx[global_var_def.attrs]
734                                .attrs
735                                .iter()
736                                .cloned()
737                                .chain([Attr::QPtr(QPtrAttr::Usage(OrdAssertEq(usage)))])
738                                .collect(),
739                        });
740                    }
741                    Err(AnalysisError(e)) => {
742                        global_var_def.attrs.push_diag(&self.cx, e);
743                    }
744                }
745            }
746        }
747        for (func, state) in self.func_states {
748            match state {
749                FuncInferUsageState::InProgress => unreachable!(),
750                FuncInferUsageState::Complete(func_results) => {
751                    let FuncInferUsageResults { param_usages, usage_or_err_attrs_to_attach } =
752                        Rc::try_unwrap(func_results).ok().unwrap();
753
754                    let func_decl = &mut module.funcs[func];
755                    for (param_decl, usage) in func_decl.params.iter_mut().zip(param_usages) {
756                        if let Some(usage) = usage {
757                            match usage {
758                                Ok(usage) => {
759                                    // FIXME(eddyb) deduplicate attribute manipulation.
760                                    param_decl.attrs = self.cx.intern(AttrSetDef {
761                                        attrs: self.cx[param_decl.attrs]
762                                            .attrs
763                                            .iter()
764                                            .cloned()
765                                            .chain([Attr::QPtr(QPtrAttr::Usage(OrdAssertEq(
766                                                usage,
767                                            )))])
768                                            .collect(),
769                                    });
770                                }
771                                Err(AnalysisError(e)) => {
772                                    param_decl.attrs.push_diag(&self.cx, e);
773                                }
774                            }
775                        }
776                    }
777
778                    let func_def_body = match &mut module.funcs[func].def {
779                        DeclDef::Present(func_def_body) => func_def_body,
780                        DeclDef::Imported(_) => continue,
781                    };
782
783                    for (v, usage) in usage_or_err_attrs_to_attach {
784                        let attrs = match v {
785                            Value::Const(_) => unreachable!(),
786                            Value::ControlRegionInput { region, input_idx } => {
787                                &mut func_def_body.at_mut(region).def().inputs[input_idx as usize]
788                                    .attrs
789                            }
790                            Value::ControlNodeOutput { control_node, output_idx } => {
791                                &mut func_def_body.at_mut(control_node).def().outputs
792                                    [output_idx as usize]
793                                    .attrs
794                            }
795                            Value::DataInstOutput(data_inst) => {
796                                &mut func_def_body.at_mut(data_inst).def().attrs
797                            }
798                        };
799                        match usage {
800                            Ok(usage) => {
801                                // FIXME(eddyb) deduplicate attribute manipulation.
802                                *attrs = self.cx.intern(AttrSetDef {
803                                    attrs: self.cx[*attrs]
804                                        .attrs
805                                        .iter()
806                                        .cloned()
807                                        .chain([Attr::QPtr(QPtrAttr::Usage(OrdAssertEq(usage)))])
808                                        .collect(),
809                                });
810                            }
811                            Err(AnalysisError(e)) => {
812                                attrs.push_diag(&self.cx, e);
813                            }
814                        }
815                    }
816                }
817            }
818        }
819    }
820
821    // HACK(eddyb) `FuncInferUsageState` also serves to indicate recursion errors.
822    fn infer_usage_in_func(&mut self, module: &Module, func: Func) -> FuncInferUsageState {
823        if let Some(cached) = self.func_states.get(&func).cloned() {
824            return cached;
825        }
826
827        self.func_states.insert(func, FuncInferUsageState::InProgress);
828
829        let completed_state =
830            FuncInferUsageState::Complete(Rc::new(self.infer_usage_in_func_uncached(module, func)));
831
832        self.func_states.insert(func, completed_state.clone());
833        completed_state
834    }
835    fn infer_usage_in_func_uncached(
836        &mut self,
837        module: &Module,
838        func: Func,
839    ) -> FuncInferUsageResults {
840        let cx = self.cx.clone();
841        let is_qptr = |ty: Type| matches!(cx[ty].kind, TypeKind::QPtr);
842
843        let func_decl = &module.funcs[func];
844        let mut param_usages: SmallVec<[_; 2]> =
845            (0..func_decl.params.len()).map(|_| None).collect();
846        let mut usage_or_err_attrs_to_attach = vec![];
847
848        let func_def_body = match &module.funcs[func].def {
849            DeclDef::Present(func_def_body) => func_def_body,
850            DeclDef::Imported(_) => {
851                for (param, param_usage) in func_decl.params.iter().zip(&mut param_usages) {
852                    if is_qptr(param.ty) {
853                        *param_usage = Some(Err(AnalysisError(Diag::bug([
854                            "pointer param of imported func".into(),
855                        ]))));
856                    }
857                }
858                return FuncInferUsageResults { param_usages, usage_or_err_attrs_to_attach };
859            }
860        };
861
862        let mut all_data_insts = CollectAllDataInsts::default();
863        func_def_body.inner_visit_with(&mut all_data_insts);
864
865        let mut data_inst_output_usages = FxHashMap::default();
866        for insts in all_data_insts.0.into_iter().rev() {
867            for func_at_inst in func_def_body.at(insts).into_iter().rev() {
868                let data_inst = func_at_inst.position;
869                let data_inst_def = func_at_inst.def();
870                let data_inst_form_def = &cx[data_inst_def.form];
871                let output_usage = data_inst_output_usages.remove(&data_inst).flatten();
872
873                let mut generate_usage = |this: &mut Self, ptr: Value, new_usage| {
874                    let slot = match ptr {
875                        Value::Const(ct) => match cx[ct].kind {
876                            ConstKind::PtrToGlobalVar(gv) => {
877                                this.global_var_usages.entry(gv).or_default()
878                            }
879                            // FIXME(eddyb) may be relevant?
880                            _ => unreachable!(),
881                        },
882                        Value::ControlRegionInput { region, input_idx }
883                            if region == func_def_body.body =>
884                        {
885                            &mut param_usages[input_idx as usize]
886                        }
887                        // FIXME(eddyb) implement
888                        Value::ControlRegionInput { .. } | Value::ControlNodeOutput { .. } => {
889                            usage_or_err_attrs_to_attach.push((
890                                ptr,
891                                Err(AnalysisError(Diag::bug(["unsupported φ".into()]))),
892                            ));
893                            return;
894                        }
895                        Value::DataInstOutput(ptr_inst) => {
896                            data_inst_output_usages.entry(ptr_inst).or_default()
897                        }
898                    };
899                    *slot = Some(match slot.take() {
900                        Some(old) => old.and_then(|old| {
901                            UsageMerger { layout_cache: &this.layout_cache }
902                                .merge(old, new_usage?)
903                                .into_result()
904                        }),
905                        None => new_usage,
906                    });
907                };
908                match &data_inst_form_def.kind {
909                    &DataInstKind::FuncCall(callee) => {
910                        match self.infer_usage_in_func(module, callee) {
911                            FuncInferUsageState::Complete(callee_results) => {
912                                for (&arg, param_usage) in
913                                    data_inst_def.inputs.iter().zip(&callee_results.param_usages)
914                                {
915                                    if let Some(param_usage) = param_usage {
916                                        generate_usage(self, arg, param_usage.clone());
917                                    }
918                                }
919                            }
920                            FuncInferUsageState::InProgress => {
921                                usage_or_err_attrs_to_attach.push((
922                                    Value::DataInstOutput(data_inst),
923                                    Err(AnalysisError(Diag::bug([
924                                        "unsupported recursive call".into()
925                                    ]))),
926                                ));
927                            }
928                        };
929                        if data_inst_form_def.output_type.map_or(false, is_qptr) {
930                            if let Some(usage) = output_usage {
931                                usage_or_err_attrs_to_attach
932                                    .push((Value::DataInstOutput(data_inst), usage));
933                            }
934                        }
935                    }
936
937                    DataInstKind::QPtr(QPtrOp::FuncLocalVar(_)) => {
938                        if let Some(usage) = output_usage {
939                            usage_or_err_attrs_to_attach
940                                .push((Value::DataInstOutput(data_inst), usage));
941                        }
942                    }
943                    DataInstKind::QPtr(QPtrOp::HandleArrayIndex) => {
944                        generate_usage(
945                            self,
946                            data_inst_def.inputs[0],
947                            output_usage
948                                .unwrap_or_else(|| {
949                                    Err(AnalysisError(Diag::bug([
950                                        "HandleArrayIndex: unknown element".into(),
951                                    ])))
952                                })
953                                .and_then(|usage| match usage {
954                                    QPtrUsage::Handles(handle) => Ok(QPtrUsage::Handles(handle)),
955                                    QPtrUsage::Memory(_) => Err(AnalysisError(Diag::bug([
956                                        "HandleArrayIndex: cannot be used as Memory".into(),
957                                    ]))),
958                                }),
959                        );
960                    }
961                    DataInstKind::QPtr(QPtrOp::BufferData) => {
962                        generate_usage(
963                            self,
964                            data_inst_def.inputs[0],
965                            output_usage
966                                .unwrap_or(Ok(QPtrUsage::Memory(QPtrMemUsage::UNUSED)))
967                                .and_then(|usage| {
968                                    let usage = match usage {
969                                        QPtrUsage::Handles(_) => {
970                                            return Err(AnalysisError(Diag::bug([
971                                                "BufferData: cannot be used as Handles".into(),
972                                            ])));
973                                        }
974                                        QPtrUsage::Memory(usage) => usage,
975                                    };
976                                    Ok(QPtrUsage::Handles(shapes::Handle::Buffer(
977                                        AddrSpace::Handles,
978                                        usage,
979                                    )))
980                                }),
981                        );
982                    }
983                    &DataInstKind::QPtr(QPtrOp::BufferDynLen {
984                        fixed_base_size,
985                        dyn_unit_stride,
986                    }) => {
987                        let array_usage = QPtrMemUsage {
988                            max_size: None,
989                            kind: QPtrMemUsageKind::DynOffsetBase {
990                                element: Rc::new(QPtrMemUsage::UNUSED),
991                                stride: dyn_unit_stride,
992                            },
993                        };
994                        let buf_data_usage = if fixed_base_size == 0 {
995                            array_usage
996                        } else {
997                            QPtrMemUsage {
998                                max_size: None,
999                                kind: QPtrMemUsageKind::OffsetBase(Rc::new(
1000                                    [(fixed_base_size, array_usage)].into(),
1001                                )),
1002                            }
1003                        };
1004                        generate_usage(
1005                            self,
1006                            data_inst_def.inputs[0],
1007                            Ok(QPtrUsage::Handles(shapes::Handle::Buffer(
1008                                AddrSpace::Handles,
1009                                buf_data_usage,
1010                            ))),
1011                        );
1012                    }
1013                    &DataInstKind::QPtr(QPtrOp::Offset(offset)) => {
1014                        generate_usage(
1015                            self,
1016                            data_inst_def.inputs[0],
1017                            output_usage
1018                                .unwrap_or(Ok(QPtrUsage::Memory(QPtrMemUsage::UNUSED)))
1019                                .and_then(|usage| {
1020                                    let usage = match usage {
1021                                        QPtrUsage::Handles(_) => {
1022                                            return Err(AnalysisError(Diag::bug([format!(
1023                                                "Offset({offset}): cannot offset Handles"
1024                                            ).into()])));
1025                                        }
1026                                        QPtrUsage::Memory(usage) => usage,
1027                                    };
1028                                    let offset = u32::try_from(offset).ok().ok_or_else(|| {
1029                                        AnalysisError(Diag::bug([format!("Offset({offset}): negative offset").into()]))
1030                                    })?;
1031
1032                                    // FIXME(eddyb) these should be normalized
1033                                    // (e.g. constant-folded) out of existence,
1034                                    // but while they exist, they should be noops.
1035                                    if offset == 0 {
1036                                        return Ok(QPtrUsage::Memory(usage));
1037                                    }
1038
1039                                    Ok(QPtrUsage::Memory(QPtrMemUsage {
1040                                        max_size: usage
1041                                            .max_size
1042                                            .map(|max_size| offset.checked_add(max_size).ok_or_else(|| {
1043                                                AnalysisError(Diag::bug([format!("Offset({offset}): size overflow ({offset}+{max_size})").into()]))
1044                                            })).transpose()?,
1045                                        // FIXME(eddyb) allocating `Rc<BTreeMap<_, _>>`
1046                                        // to represent the one-element case, seems
1047                                        // quite wasteful when it's likely consumed.
1048                                        kind: QPtrMemUsageKind::OffsetBase(Rc::new(
1049                                            [(offset, usage)].into(),
1050                                        )),
1051                                    }))
1052                                }),
1053                        );
1054                    }
1055                    DataInstKind::QPtr(QPtrOp::DynOffset { stride, index_bounds }) => {
1056                        generate_usage(
1057                            self,
1058                            data_inst_def.inputs[0],
1059                            output_usage
1060                                .unwrap_or(Ok(QPtrUsage::Memory(QPtrMemUsage::UNUSED)))
1061                                .and_then(|usage| {
1062                                    let usage = match usage {
1063                                        QPtrUsage::Handles(_) => {
1064                                            return Err(AnalysisError(Diag::bug(["DynOffset: cannot offset Handles".into()])));
1065                                        }
1066                                        QPtrUsage::Memory(usage) => usage,
1067                                    };
1068                                    match usage.max_size {
1069                                        None => {
1070                                            return Err(AnalysisError(Diag::bug(["DynOffset: unsized element".into()])));
1071                                        }
1072                                        // FIXME(eddyb) support this by "folding"
1073                                        // the usage onto itself (i.e. applying
1074                                        // `%= stride` on all offsets inside).
1075                                        Some(max_size) if max_size > stride.get() => {
1076                                            return Err(AnalysisError(Diag::bug(["DynOffset: element max_size exceeds stride".into()])));
1077                                        }
1078                                        Some(_) => {}
1079                                    }
1080                                    Ok(QPtrUsage::Memory(QPtrMemUsage {
1081                                        // FIXME(eddyb) does the `None` case allow
1082                                        // for negative offsets?
1083                                        max_size: index_bounds
1084                                            .as_ref()
1085                                            .map(|index_bounds| {
1086                                                if index_bounds.start < 0 || index_bounds.end < 0 {
1087                                                    return Err(AnalysisError(
1088                                                        Diag::bug([
1089                                                            "DynOffset: potentially negative offset"
1090                                                                .into(),
1091                                                        ])
1092                                                    ));
1093                                                }
1094                                                let index_bounds_end = u32::try_from(index_bounds.end).unwrap();
1095                                                index_bounds_end.checked_mul(stride.get()).ok_or_else(|| {
1096                                                     AnalysisError(Diag::bug([
1097                                                        format!("DynOffset: size overflow ({index_bounds_end}*{stride})").into(),
1098                                                    ]))
1099                                                })
1100                                            })
1101                                            .transpose()?,
1102                                        kind: QPtrMemUsageKind::DynOffsetBase {
1103                                            element: Rc::new(usage),
1104                                            stride: *stride,
1105                                        },
1106                                    }))
1107                                }),
1108                        );
1109                    }
1110                    DataInstKind::QPtr(op @ (QPtrOp::Load | QPtrOp::Store)) => {
1111                        let (op_name, access_type) = match op {
1112                            QPtrOp::Load => ("Load", data_inst_form_def.output_type.unwrap()),
1113                            QPtrOp::Store => {
1114                                ("Store", func_at_inst.at(data_inst_def.inputs[1]).type_of(&cx))
1115                            }
1116                            _ => unreachable!(),
1117                        };
1118                        generate_usage(
1119                            self,
1120                            data_inst_def.inputs[0],
1121                            self.layout_cache
1122                                .layout_of(access_type)
1123                                .map_err(|LayoutError(e)| AnalysisError(e))
1124                                .and_then(|layout| match layout {
1125                                    TypeLayout::Handle(shapes::Handle::Opaque(ty)) => {
1126                                        Ok(QPtrUsage::Handles(shapes::Handle::Opaque(ty)))
1127                                    }
1128                                    TypeLayout::Handle(shapes::Handle::Buffer(..)) => {
1129                                        Err(AnalysisError(Diag::bug([format!(
1130                                            "{op_name}: cannot access whole Buffer"
1131                                        )
1132                                        .into()])))
1133                                    }
1134                                    TypeLayout::HandleArray(..) => {
1135                                        Err(AnalysisError(Diag::bug([format!(
1136                                            "{op_name}: cannot access whole HandleArray"
1137                                        )
1138                                        .into()])))
1139                                    }
1140                                    TypeLayout::Concrete(concrete)
1141                                        if concrete.mem_layout.dyn_unit_stride.is_some() =>
1142                                    {
1143                                        Err(AnalysisError(Diag::bug([format!(
1144                                            "{op_name}: cannot access unsized type"
1145                                        )
1146                                        .into()])))
1147                                    }
1148                                    TypeLayout::Concrete(concrete) => {
1149                                        Ok(QPtrUsage::Memory(QPtrMemUsage {
1150                                            max_size: Some(concrete.mem_layout.fixed_base.size),
1151                                            kind: QPtrMemUsageKind::DirectAccess(access_type),
1152                                        }))
1153                                    }
1154                                }),
1155                        );
1156                    }
1157
1158                    DataInstKind::SpvInst(_) | DataInstKind::SpvExtInst { .. } => {
1159                        let mut has_from_spv_ptr_output_attr = false;
1160                        for attr in &cx[data_inst_def.attrs].attrs {
1161                            match *attr {
1162                                Attr::QPtr(QPtrAttr::ToSpvPtrInput { input_idx, pointee }) => {
1163                                    let ty = pointee.0;
1164                                    generate_usage(
1165                                        self,
1166                                        data_inst_def.inputs[input_idx as usize],
1167                                        self.layout_cache
1168                                            .layout_of(ty)
1169                                            .map_err(|LayoutError(e)| AnalysisError(e))
1170                                            .and_then(|layout| match layout {
1171                                                TypeLayout::Handle(handle) => {
1172                                                    let handle = match handle {
1173                                                        shapes::Handle::Opaque(ty) => {
1174                                                            shapes::Handle::Opaque(ty)
1175                                                        }
1176                                                        // NOTE(eddyb) this error is important,
1177                                                        // as the `Block` annotation on the
1178                                                        // buffer type means the type is *not*
1179                                                        // usable anywhere inside buffer data,
1180                                                        // since it would conflict with our
1181                                                        // own `Block`-annotated wrapper.
1182                                                        shapes::Handle::Buffer(..) => {
1183                                                            return Err(AnalysisError(Diag::bug(["ToSpvPtrInput: whole Buffer ambiguous (handle vs buffer data)".into()])
1184                                                            ));
1185                                                        }
1186                                                    };
1187                                                    Ok(QPtrUsage::Handles(handle))
1188                                                }
1189                                                // NOTE(eddyb) because we can't represent
1190                                                // the original type, in the same way we
1191                                                // use `QPtrMemUsageKind::StrictlyTyped`
1192                                                // for non-handles, we can't guarantee
1193                                                // a generated type that matches the
1194                                                // desired `pointee` type.
1195                                                TypeLayout::HandleArray(..) => {
1196                                                    Err(AnalysisError(Diag::bug(["ToSpvPtrInput: whole handle array unrepresentable".into()])
1197                                                    ))
1198                                                }
1199                                                TypeLayout::Concrete(concrete) => {
1200                                                    Ok(QPtrUsage::Memory(QPtrMemUsage {
1201                                                        max_size: if concrete
1202                                                            .mem_layout
1203                                                            .dyn_unit_stride
1204                                                            .is_some()
1205                                                        {
1206                                                            None
1207                                                        } else {
1208                                                            Some(
1209                                                                concrete.mem_layout.fixed_base.size,
1210                                                            )
1211                                                        },
1212                                                        kind: QPtrMemUsageKind::StrictlyTyped(ty),
1213                                                    }))
1214                                                }
1215                                            }),
1216                                    );
1217                                }
1218                                Attr::QPtr(QPtrAttr::FromSpvPtrOutput {
1219                                    addr_space: _,
1220                                    pointee: _,
1221                                }) => {
1222                                    has_from_spv_ptr_output_attr = true;
1223                                }
1224                                _ => {}
1225                            }
1226                        }
1227
1228                        if has_from_spv_ptr_output_attr {
1229                            // FIXME(eddyb) merge with `FromSpvPtrOutput`'s `pointee`.
1230                            if let Some(usage) = output_usage {
1231                                usage_or_err_attrs_to_attach
1232                                    .push((Value::DataInstOutput(data_inst), usage));
1233                            }
1234                        }
1235                    }
1236                }
1237            }
1238        }
1239
1240        FuncInferUsageResults { param_usages, usage_or_err_attrs_to_attach }
1241    }
1242}
1243
1244// HACK(eddyb) this is easier than implementing a proper reverse traversal.
1245#[derive(Default)]
1246struct CollectAllDataInsts(Vec<EntityList<DataInst>>);
1247
1248impl Visitor<'_> for CollectAllDataInsts {
1249    // FIXME(eddyb) this is excessive, maybe different kinds of
1250    // visitors should exist for module-level and func-level?
1251    fn visit_attr_set_use(&mut self, _: AttrSet) {}
1252    fn visit_type_use(&mut self, _: Type) {}
1253    fn visit_const_use(&mut self, _: Const) {}
1254    fn visit_data_inst_form_use(&mut self, _: DataInstForm) {}
1255    fn visit_global_var_use(&mut self, _: GlobalVar) {}
1256    fn visit_func_use(&mut self, _: Func) {}
1257
1258    fn visit_control_node_def(&mut self, func_at_control_node: FuncAt<'_, ControlNode>) {
1259        if let ControlNodeKind::Block { insts } = func_at_control_node.def().kind {
1260            self.0.push(insts);
1261        }
1262        func_at_control_node.inner_visit_with(self);
1263    }
1264}