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}