1use crate::qptr::shapes;
4use crate::{
5 AddrSpace, Attr, Const, ConstKind, Context, Diag, FxIndexMap, Type, TypeKind, TypeOrConst, spv,
6};
7use itertools::Either;
8use smallvec::SmallVec;
9use std::cell::RefCell;
10use std::cmp::Ordering;
11use std::num::NonZeroU32;
12use std::ops::Range;
13use std::rc::Rc;
14
15pub struct LayoutConfig {
20 pub ignore_legacy_align: bool,
21 pub min_aggregate_legacy_align: u32,
22
23 pub abstract_bool_size_align: (u32, u32),
34
35 pub logical_ptr_size_align: (u32, u32),
46}
47
48impl LayoutConfig {
49 pub const VULKAN_SCALAR_LAYOUT: Self = Self {
50 ignore_legacy_align: true,
51 min_aggregate_legacy_align: 1,
52
53 abstract_bool_size_align: (1, 1),
54 logical_ptr_size_align: (1, 1),
55 };
56 pub const VULKAN_STANDARD_LAYOUT: Self =
57 Self { ignore_legacy_align: false, ..Self::VULKAN_SCALAR_LAYOUT };
58 pub const VULKAN_EXTENDED_ALIGN_UBO_LAYOUT: Self =
61 Self { min_aggregate_legacy_align: 16, ..Self::VULKAN_STANDARD_LAYOUT };
62}
63
64pub(super) struct LayoutError(pub(super) Diag);
65
66#[derive(Clone)]
67pub(super) enum TypeLayout {
68 Handle(HandleLayout),
69 HandleArray(HandleLayout, Option<NonZeroU32>),
70
71 Concrete(Rc<MemTypeLayout>),
73}
74
75pub(super) type HandleLayout = shapes::Handle<Rc<MemTypeLayout>>;
77
78pub(super) struct MemTypeLayout {
79 pub(super) original_type: Type,
80 pub(super) mem_layout: shapes::MaybeDynMemLayout,
81 pub(super) components: Components,
82}
83
84pub(super) enum Components {
86 Scalar,
87
88 Elements {
90 stride: NonZeroU32,
91 elem: Rc<MemTypeLayout>,
92 fixed_len: Option<NonZeroU32>,
93 },
94
95 Fields {
96 offsets: SmallVec<[u32; 4]>,
98 layouts: SmallVec<[Rc<MemTypeLayout>; 4]>,
99 },
100}
101
102impl Components {
103 pub(super) fn find_components_containing(
110 &self,
111 offset_range: Range<u32>,
113 ) -> impl Iterator<Item = usize> + '_ {
114 match self {
115 Components::Scalar => Either::Left(None.into_iter()),
116 Components::Elements { stride, elem, fixed_len } => {
117 Either::Left(
118 Some(offset_range.start / stride.get())
119 .and_then(|elem_idx| {
120 let elem_idx_vs_len = fixed_len
121 .map_or(Ordering::Less, |fixed_len| elem_idx.cmp(&fixed_len.get()));
122 let elem_size = match elem_idx_vs_len {
123 Ordering::Less => elem.mem_layout.fixed_base.size,
124
125 Ordering::Equal => 0,
127
128 Ordering::Greater => return None,
129 };
130 let elem_start = elem_idx * stride.get();
131 Some((elem_idx, elem_start..elem_start.checked_add(elem_size)?))
132 })
133 .filter(|(_, elem_range)| offset_range.end <= elem_range.end)
134 .and_then(|(elem_idx, _)| usize::try_from(elem_idx).ok())
135 .into_iter(),
136 )
137 }
138 Components::Fields { offsets, layouts } => Either::Right(
142 offsets
143 .iter()
144 .zip(layouts)
145 .map(|(&field_offset, field)| {
146 if field.mem_layout.dyn_unit_stride.is_some() {
148 Err(field_offset..)
149 } else {
150 Ok(field_offset
151 ..field_offset
152 .checked_add(field.mem_layout.fixed_base.size)
153 .unwrap())
154 }
155 })
156 .enumerate()
157 .filter(move |(_, field_range)| match field_range {
158 Ok(field_range) => {
159 field_range.start <= offset_range.start
160 && offset_range.end <= field_range.end
161 }
162 Err(field_range) => field_range.start <= offset_range.start,
163 })
164 .map(|(field_idx, _)| field_idx),
165 ),
166 }
167 }
168}
169
170pub(super) struct LayoutCache<'a> {
172 cx: Rc<Context>,
173 wk: &'static spv::spec::WellKnown,
174
175 config: &'a LayoutConfig,
176
177 cache: RefCell<FxIndexMap<Type, TypeLayout>>,
178}
179
180impl<'a> LayoutCache<'a> {
181 pub(super) fn new(cx: Rc<Context>, config: &'a LayoutConfig) -> Self {
182 Self { cx, wk: &spv::spec::Spec::get().well_known, config, cache: Default::default() }
183 }
184
185 fn const_as_u32(&self, ct: Const) -> Option<u32> {
187 if let ConstKind::SpvInst { spv_inst_and_const_inputs } = &self.cx[ct].kind {
188 let (spv_inst, _const_inputs) = &**spv_inst_and_const_inputs;
189 if spv_inst.opcode == self.wk.OpConstant && spv_inst.imms.len() == 1 {
190 match spv_inst.imms[..] {
191 [spv::Imm::Short(_, x)] => return Some(x),
192 _ => unreachable!(),
193 }
194 }
195 }
196 None
197 }
198
199 pub(super) fn layout_of(&self, ty: Type) -> Result<TypeLayout, LayoutError> {
201 if let Some(cached) = self.cache.borrow().get(&ty).cloned() {
202 return Ok(cached);
203 }
204
205 let cx = &self.cx;
206 let wk = self.wk;
207
208 let ty_def = &cx[ty];
209 let (spv_inst, type_and_const_inputs) = match &ty_def.kind {
210 TypeKind::QPtr => {
212 return Err(LayoutError(Diag::bug(
213 ["`layout_of(qptr)` (already lowered?)".into()],
214 )));
215 }
216 TypeKind::SpvInst { spv_inst, type_and_const_inputs } => {
217 (spv_inst, type_and_const_inputs)
218 }
219 TypeKind::SpvStringLiteralForExtInst => {
220 return Err(LayoutError(Diag::bug([
221 "`layout_of(type_of(OpString<\"...\">))`".into()
222 ])));
223 }
224 };
225
226 let scalar_with_size_and_align = |(size, align)| {
227 TypeLayout::Concrete(Rc::new(MemTypeLayout {
228 original_type: ty,
229 mem_layout: shapes::MaybeDynMemLayout {
230 fixed_base: shapes::MemLayout { align, legacy_align: align, size },
231 dyn_unit_stride: None,
232 },
233 components: Components::Scalar,
234 }))
235 };
236 let scalar = |width: u32| {
237 assert!(width.is_power_of_two());
238 let size = width / 8;
239 assert_eq!(size * 8, width);
240 scalar_with_size_and_align((size, size))
241 };
242 let align_to = |size: u32, align: u32| {
243 assert!(align.is_power_of_two() && align > 0);
244 Ok(size.checked_add(align - 1).ok_or_else(|| {
245 LayoutError(Diag::bug([
246 format!("`align_to({size}, {align})` overflowed `u32`").into()
247 ]))
248 })? & !(align - 1))
249 };
250 struct ArrayParams {
252 fixed_len: Option<u32>,
253 known_stride: Option<u32>,
254 min_legacy_align: u32,
255 legacy_align_multiplier: u32,
256 }
257 let array = |elem_type: Type,
258 ArrayParams {
259 fixed_len,
260 known_stride,
261 min_legacy_align,
262 legacy_align_multiplier,
263 }| {
264 let fixed_len = fixed_len
265 .map(|x| {
266 NonZeroU32::new(x).ok_or_else(|| {
267 LayoutError(Diag::err(["SPIR-V disallows arrays of `0` length".into()]))
268 })
269 })
270 .transpose()?;
271 match self.layout_of(elem_type)? {
272 TypeLayout::Handle(handle) => Ok(TypeLayout::HandleArray(handle, fixed_len)),
273 TypeLayout::HandleArray(..) => Err(LayoutError(Diag::err([
274 "handle array `".into(),
275 elem_type.into(),
276 "` cannot be further wrapped in an array".into(),
277 ]))),
278 TypeLayout::Concrete(elem) => {
279 if elem.mem_layout.dyn_unit_stride.is_some() {
280 return Err(LayoutError(Diag::err([
281 "dynamically sized type `".into(),
282 elem_type.into(),
283 "` cannot be further wrapped in an array".into(),
284 ])));
285 }
286 let stride = match known_stride {
287 Some(stride) => stride,
288 None => {
289 let shapes::MemLayout { align, legacy_align, size } =
290 elem.mem_layout.fixed_base;
291 let (stride, legacy_stride) =
292 (align_to(size, align)?, align_to(size, legacy_align)?);
293
294 if !self.config.ignore_legacy_align && stride != legacy_stride {
298 return Err(LayoutError(Diag::bug([format!(
299 "ambiguous stride: \
300 {stride} (scalar) vs {legacy_stride} (legacy), \
301 due to alignment differences \
302 ({align} scalar vs {legacy_align} legacy)",
303 )
304 .into()])));
305 }
306 stride
307 }
308 };
309 let stride = NonZeroU32::new(stride).ok_or_else(|| {
310 LayoutError(Diag::err(["SPIR-V disallows arrays of `0` stride".into()]))
311 })?;
312 Ok(TypeLayout::Concrete(Rc::new(MemTypeLayout {
313 original_type: ty,
314 mem_layout: shapes::MaybeDynMemLayout {
315 fixed_base: shapes::MemLayout {
316 align: elem.mem_layout.fixed_base.align,
317 legacy_align: elem
318 .mem_layout
319 .fixed_base
320 .legacy_align
321 .checked_mul(legacy_align_multiplier)
322 .unwrap()
323 .max(min_legacy_align),
324 size: fixed_len
325 .map(|len| {
326 stride.checked_mul(len).ok_or_else(|| {
327 LayoutError(Diag::bug([format!(
328 "`{stride} * {len}` overflowed `u32`"
329 )
330 .into()]))
331 })
332 })
333 .transpose()?
334 .map_or(0, |size| size.get()),
335 },
336 dyn_unit_stride: if fixed_len.is_none() { Some(stride) } else { None },
337 },
338 components: Components::Elements { stride, elem, fixed_len },
339 })))
340 }
341 }
342 };
343 let short_imm_at = |i| match spv_inst.imms[i] {
344 spv::Imm::Short(_, x) => x,
345 _ => unreachable!(),
346 };
347
348 let layout = if spv_inst.opcode == wk.OpTypeBool {
353 scalar_with_size_and_align(self.config.abstract_bool_size_align)
355 } else if spv_inst.opcode == wk.OpTypePointer {
356 scalar_with_size_and_align(self.config.logical_ptr_size_align)
360 } else if [wk.OpTypeInt, wk.OpTypeFloat].contains(&spv_inst.opcode) {
361 scalar(short_imm_at(0))
362 } else if [wk.OpTypeVector, wk.OpTypeMatrix].contains(&spv_inst.opcode) {
363 let len = short_imm_at(0);
364 let (min_legacy_align, legacy_align_multiplier) = if spv_inst.opcode == wk.OpTypeVector
365 {
366 (1, if len <= 2 { 2 } else { 4 })
368 } else {
369 (self.config.min_aggregate_legacy_align, 1)
370 };
371 array(
373 match type_and_const_inputs[..] {
374 [TypeOrConst::Type(elem_type)] => elem_type,
375 _ => unreachable!(),
376 },
377 ArrayParams {
378 fixed_len: Some(len),
379 known_stride: None,
380 min_legacy_align,
381 legacy_align_multiplier,
382 },
383 )?
384 } else if [wk.OpTypeArray, wk.OpTypeRuntimeArray].contains(&spv_inst.opcode) {
385 let len = type_and_const_inputs
386 .get(1)
387 .map(|&len| {
388 let len = match len {
389 TypeOrConst::Const(len) => len,
390 TypeOrConst::Type(_) => unreachable!(),
391 };
392 self.const_as_u32(len).ok_or_else(|| {
393 LayoutError(Diag::bug(
394 ["specialization constants not supported yet".into()],
395 ))
396 })
397 })
398 .transpose()?;
399 let mut stride_decoration = None;
400 for attr in &cx[ty_def.attrs].attrs {
401 match attr {
402 Attr::SpvAnnotation(attr_spv_inst)
403 if attr_spv_inst.opcode == wk.OpDecorate
404 && attr_spv_inst.imms[0]
405 == spv::Imm::Short(wk.Decoration, wk.ArrayStride) =>
406 {
407 stride_decoration = Some(match attr_spv_inst.imms[1] {
408 spv::Imm::Short(_, x) => x,
409 _ => unreachable!(),
410 });
411 break;
412 }
413 _ => {}
414 }
415 }
416 array(
417 match type_and_const_inputs[0] {
418 TypeOrConst::Type(elem_type) => elem_type,
419 TypeOrConst::Const(_) => unreachable!(),
420 },
421 ArrayParams {
422 fixed_len: len,
423 known_stride: stride_decoration,
424 min_legacy_align: self.config.min_aggregate_legacy_align,
425 legacy_align_multiplier: 1,
426 },
427 )?
428 } else if spv_inst.opcode == wk.OpTypeStruct {
429 let field_layouts: SmallVec<[_; 4]> = type_and_const_inputs
430 .iter()
431 .map(|&ty_or_ct| match ty_or_ct {
432 TypeOrConst::Type(field_type) => field_type,
433 TypeOrConst::Const(_) => unreachable!(),
434 })
435 .map(|field_type| match self.layout_of(field_type)? {
436 TypeLayout::Handle(_) | TypeLayout::HandleArray(..) => {
437 Err(LayoutError(Diag::bug([
438 "handles cannot be placed in a struct field".into()
439 ])))
440 }
441 TypeLayout::Concrete(field_layout) => Ok(field_layout),
442 })
443 .collect::<Result<_, _>>()?;
444
445 let mut field_offsets: SmallVec<[_; 4]> = SmallVec::with_capacity(field_layouts.len());
446 for attr in &cx[ty_def.attrs].attrs {
447 match attr {
448 Attr::SpvAnnotation(attr_spv_inst)
449 if attr_spv_inst.opcode == wk.OpMemberDecorate
450 && attr_spv_inst.imms[1]
451 == spv::Imm::Short(wk.Decoration, wk.RowMajor) =>
452 {
453 return Err(LayoutError(Diag::bug([
454 "`RowMajor` matrix types unsupported".into(),
455 ])));
456 }
457 Attr::SpvAnnotation(attr_spv_inst)
458 if attr_spv_inst.opcode == wk.OpMemberDecorate
459 && attr_spv_inst.imms[1]
460 == spv::Imm::Short(wk.Decoration, wk.Offset) =>
461 {
462 let (field_idx, field_offset) = match attr_spv_inst.imms[..] {
463 [spv::Imm::Short(_, idx), _, spv::Imm::Short(_, offset)] => {
464 (idx, offset)
465 }
466 _ => unreachable!(),
467 };
468 let field_idx = usize::try_from(field_idx).unwrap();
469 match field_idx.cmp(&field_offsets.len()) {
470 Ordering::Less => {
471 return Err(LayoutError(Diag::bug([
472 "a struct field cannot have more than one explicit offset"
473 .into(),
474 ])));
475 }
476 Ordering::Greater => {
477 return Err(LayoutError(Diag::bug([
478 "structs with explicit offsets must provide them for all fields"
479 .into(),
480 ])));
481 }
482 Ordering::Equal => {
483 field_offsets.push(field_offset);
484 }
485 }
486 }
487 _ => {}
488 }
489 }
490 let mut mem_layout = shapes::MaybeDynMemLayout {
491 fixed_base: shapes::MemLayout {
492 align: 1,
493 legacy_align: self.config.min_aggregate_legacy_align,
494 size: 0,
495 },
496 dyn_unit_stride: None,
497 };
498 if !field_offsets.is_empty() {
499 if field_offsets.len() != field_layouts.len() {
500 return Err(LayoutError(Diag::bug([
501 "structs with explicit offsets must provide them for all fields".into(),
502 ])));
503 }
504
505 for (&field_offset, field_layout) in field_offsets.iter().zip(&field_layouts) {
512 let field = field_layout.mem_layout;
513
514 mem_layout.fixed_base.align =
515 mem_layout.fixed_base.align.max(field.fixed_base.align);
516 mem_layout.fixed_base.legacy_align =
517 mem_layout.fixed_base.legacy_align.max(field.fixed_base.legacy_align);
518 mem_layout.fixed_base.size = mem_layout.fixed_base.size.max(
519 field_offset.checked_add(field.fixed_base.size).ok_or_else(|| {
520 LayoutError(Diag::bug([format!(
521 "`{} + {}` overflowed `u32`",
522 field_offset, field.fixed_base.size
523 )
524 .into()]))
525 })?,
526 );
527
528 if let Some(field_dyn_unit_stride) = field.dyn_unit_stride {
530 if mem_layout.dyn_unit_stride.is_some() {
531 return Err(LayoutError(Diag::bug([
532 "only one field of a struct can have a dynamically sized type"
533 .into(),
534 ])));
535 }
536 mem_layout.dyn_unit_stride = Some(field_dyn_unit_stride);
537 }
538 }
539 } else {
540 for field_layout in &field_layouts {
541 if mem_layout.dyn_unit_stride.is_some() {
542 return Err(LayoutError(Diag::bug([
543 "only the last field of a struct can have a dynamically sized type"
544 .into(),
545 ])));
546 }
547
548 let field = field_layout.mem_layout;
549
550 let (offset, legacy_offset) = (
551 align_to(mem_layout.fixed_base.size, field.fixed_base.align)?,
552 align_to(mem_layout.fixed_base.size, field.fixed_base.legacy_align)?,
553 );
554 if !self.config.ignore_legacy_align && offset != legacy_offset {
558 return Err(LayoutError(Diag::bug([format!(
559 "ambiguous offset: {offset} (scalar) vs {legacy_offset} (legacy), \
560 due to alignment differences ({} scalar vs {} legacy)",
561 field.fixed_base.align, field.fixed_base.legacy_align
562 )
563 .into()])));
564 }
565
566 field_offsets.push(offset);
567
568 mem_layout.fixed_base.align =
569 mem_layout.fixed_base.align.max(field.fixed_base.align);
570 mem_layout.fixed_base.legacy_align =
571 mem_layout.fixed_base.legacy_align.max(field.fixed_base.legacy_align);
572 mem_layout.fixed_base.size =
573 offset.checked_add(field.fixed_base.size).ok_or_else(|| {
574 LayoutError(Diag::bug([format!(
575 "`{} + {}` overflowed `u32`",
576 offset, field.fixed_base.size
577 )
578 .into()]))
579 })?;
580
581 assert!(mem_layout.dyn_unit_stride.is_none());
582 mem_layout.dyn_unit_stride = field.dyn_unit_stride;
583 }
584 }
585 if mem_layout.dyn_unit_stride.is_none() {
587 mem_layout.fixed_base.size =
588 align_to(mem_layout.fixed_base.size, mem_layout.fixed_base.align)?;
589 }
590
591 let concrete = Rc::new(MemTypeLayout {
592 original_type: ty,
593 mem_layout,
594 components: Components::Fields { offsets: field_offsets, layouts: field_layouts },
595 });
596 let mut is_interface_block = false;
597 for attr in &cx[ty_def.attrs].attrs {
598 match attr {
599 Attr::SpvAnnotation(attr_spv_inst)
600 if attr_spv_inst.opcode == wk.OpDecorate
601 && attr_spv_inst.imms[0]
602 == spv::Imm::Short(wk.Decoration, wk.Block) =>
603 {
604 is_interface_block = true;
605 break;
606 }
607 _ => {}
608 }
609 }
610 if is_interface_block {
625 TypeLayout::Handle(shapes::Handle::Buffer(AddrSpace::Handles, concrete))
627 } else {
628 TypeLayout::Concrete(concrete)
629 }
630 } else if [
631 wk.OpTypeImage,
632 wk.OpTypeSampler,
633 wk.OpTypeSampledImage,
634 wk.OpTypeAccelerationStructureKHR,
635 ]
636 .contains(&spv_inst.opcode)
637 {
638 TypeLayout::Handle(shapes::Handle::Opaque(ty))
639 } else {
640 return Err(LayoutError(Diag::bug([format!(
641 "unknown/unsupported SPIR-V type `{}`",
642 spv_inst.opcode.name()
643 )
644 .into()])));
645 };
646 self.cache.borrow_mut().insert(ty, layout.clone());
647 Ok(layout)
648 }
649}