From f2b51c121ed3deac47d58f46905c7c8d180a6e9c Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 06:07:54 +0200 Subject: [PATCH 001/182] MVP type layout 2 --- examples/api_showcase/src/main.rs | 2 +- .../hello_triangles/src/util/shame_glam.rs | 6 +- examples/shame_wgpu/src/conversion.rs | 14 +- examples/type_layout/Cargo.toml | 7 + examples/type_layout/src/main.rs | 112 +++ shame/src/common/po2.rs | 38 +- shame/src/common/proc_macro_reexports.rs | 10 +- shame/src/common/proc_macro_utils.rs | 5 +- shame/src/frontend/any/render_io.rs | 17 +- shame/src/frontend/encoding/buffer.rs | 21 +- shame/src/frontend/encoding/io_iter.rs | 8 +- shame/src/frontend/rust_types/array.rs | 27 +- shame/src/frontend/rust_types/atomic.rs | 21 +- .../src/frontend/rust_types/layout_traits.rs | 95 +- shame/src/frontend/rust_types/mat.rs | 29 +- shame/src/frontend/rust_types/mod.rs | 2 +- shame/src/frontend/rust_types/packed_vec.rs | 34 +- shame/src/frontend/rust_types/struct_.rs | 18 +- shame/src/frontend/rust_types/type_layout.rs | 906 ------------------ .../type_layout/cpu_shareable/align_size.rs | 298 ++++++ .../type_layout/cpu_shareable/builder.rs | 165 ++++ .../type_layout/cpu_shareable/mod.rs | 493 ++++++++++ .../src/frontend/rust_types/type_layout/eq.rs | 452 +++++++++ .../frontend/rust_types/type_layout/mod.rs | 616 ++++++++++++ .../rust_types/type_layout/storage_uniform.rs | 475 +++++++++ .../frontend/rust_types/type_layout/vertex.rs | 100 ++ shame/src/frontend/rust_types/type_traits.rs | 21 +- shame/src/frontend/rust_types/vec.rs | 37 +- shame/src/ir/ir_type/align_size.rs | 4 +- shame/src/ir/ir_type/layout_constraints.rs | 6 +- shame/src/ir/ir_type/tensor.rs | 78 +- shame/src/ir/pipeline/wip_pipeline.rs | 17 +- shame/src/lib.rs | 11 +- shame/tests/test_layout.rs | 4 +- shame_derive/src/derive_layout.rs | 105 +- shame_derive/src/util.rs | 20 +- 36 files changed, 3163 insertions(+), 1111 deletions(-) create mode 100644 examples/type_layout/Cargo.toml create mode 100644 examples/type_layout/src/main.rs delete mode 100644 shame/src/frontend/rust_types/type_layout.rs create mode 100644 shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs create mode 100644 shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs create mode 100644 shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs create mode 100644 shame/src/frontend/rust_types/type_layout/eq.rs create mode 100644 shame/src/frontend/rust_types/type_layout/mod.rs create mode 100644 shame/src/frontend/rust_types/type_layout/storage_uniform.rs create mode 100644 shame/src/frontend/rust_types/type_layout/vertex.rs diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index a1b3eb8..f8e2d88 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -509,4 +509,4 @@ impl sm::CpuLayout for Mat4 { // // tell `shame` about the layout semantics of `glam` types // impl MyCpuLayoutTrait for glam::Mat4 { // fn layout() -> shame::TypeLayout { sm::f32x4x4::layout() } -// } +// } \ No newline at end of file diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index 7f85092..6cf3441 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout().into() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - sm::f32x2::gpu_layout() + sm::f32x2::gpu_layout().into() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout().into() } } diff --git a/examples/shame_wgpu/src/conversion.rs b/examples/shame_wgpu/src/conversion.rs index 16c7ad6..05b6d73 100644 --- a/examples/shame_wgpu/src/conversion.rs +++ b/examples/shame_wgpu/src/conversion.rs @@ -101,7 +101,7 @@ pub fn sample_type(st: sm::TextureSampleUsageType) -> wgpu::TextureSampleType { } } -/// converts `tf` into a `wgpu::TextureFormat` if supported. +/// converts `tf` into a `wgpu::TextureFormat` if supported. /// If `tf` is `ExtraTextureFormats::SurfaceFormat`, then the provided `surface_format` argument /// is returned if it is `Some`. Otherwise an error is returned. #[rustfmt::skip] @@ -194,7 +194,7 @@ pub fn texture_format(tf: &dyn sm::TextureFormatId, surface_format: Option wgpu::TextureFormat::EacR11Snorm, SmTf::EacRg11Unorm => wgpu::TextureFormat::EacRg11Unorm, SmTf::EacRg11Snorm => wgpu::TextureFormat::EacRg11Snorm, - SmTf::Astc { block, channel } => wgpu::TextureFormat::Astc { + SmTf::Astc { block, channel } => wgpu::TextureFormat::Astc { block: match block { SmASTCb::B4x4 => wgpu::AstcBlock::B4x4, SmASTCb::B5x4 => wgpu::AstcBlock::B5x4, @@ -210,12 +210,12 @@ pub fn texture_format(tf: &dyn sm::TextureFormatId, surface_format: Option wgpu::AstcBlock::B10x10, SmASTCb::B12x10 => wgpu::AstcBlock::B12x10, SmASTCb::B12x12 => wgpu::AstcBlock::B12x12, - }, + }, channel: match channel { SmASTCc::Unorm => wgpu::AstcChannel::Unorm, SmASTCc::UnormSrgb => wgpu::AstcChannel::UnormSrgb, SmASTCc::Hdr => wgpu::AstcChannel::Hdr, - } + } }, }; Ok(wtf) @@ -449,7 +449,7 @@ fn color_writes(write_mask: smr::ChannelWrites) -> wgpu::ColorWrites { #[rustfmt::skip] fn vertex_format(format: smr::VertexAttribFormat) -> Result { - use smr::ScalarType as S; + use shame::cpu_shareable::ScalarType as S; use smr::Len as L; use wgpu::VertexFormat as W; let unsupported = Err(ShameToWgpuError::UnsupportedVertexAttribFormat(format)); @@ -479,10 +479,8 @@ fn vertex_format(format: smr::VertexAttribFormat) -> Result W::Sint32x2, (S::I32, L::X3) => W::Sint32x3, (S::I32, L::X4) => W::Sint32x4, - - (S::Bool, _) => return unsupported, }, - + smr::VertexAttribFormat::Coarse(p) => { use smr::PackedScalarType as PS; use smr::PackedFloat as Norm; diff --git a/examples/type_layout/Cargo.toml b/examples/type_layout/Cargo.toml new file mode 100644 index 0000000..9d53846 --- /dev/null +++ b/examples/type_layout/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "type_layout2" +version = "0.1.0" +edition = "2024" + +[dependencies] +shame = { path = "../../shame/" } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs new file mode 100644 index 0000000..2b37fdf --- /dev/null +++ b/examples/type_layout/src/main.rs @@ -0,0 +1,112 @@ +#![allow(dead_code, unused)] +//! Demonstration of the TypeLayout and TypeLayout Builder API. + +use shame::{ + any::{self, U32PowerOf2}, + boolx1, + cpu_shareable::{self as cs, BinaryReprSized}, + f32x1, f32x2, f32x3, f32x4, + type_layout::*, + Array, GpuLayout, GpuSized, VertexAttribute, VertexLayout, +}; + +fn main() { + // We'll start by building a `TypeLayout`, which can be used for ... nothing really + #[derive(GpuLayout)] + struct Vertex { + position: f32x3, + normal: f32x3, + uv: f32x1, + } + + // TypeLayout::vertex_builder immediately takes the first field of the struct, because + // structs need to have at least one field. + let mut builder = + TypeLayout::vertex_builder("Vertex", "position", f32x4::vertex_attrib_format(), cs::Repr::Storage) + .extend("normal", f32x3::vertex_attrib_format()) + .extend("uv", f32x1::vertex_attrib_format()) + .finish(); + + + // Now we'll replicate the layout of this struct + #[derive(GpuLayout)] + struct A { + a: f32x4, + b: f32x3, + c: Array, + } + + // Be default structs are #[gpu_repr(Storage)], which means that it follows + // the wgsl storage layout rules (std430). To obtain a corresponding TypeLayout + // we first need to build a `CpuShareableType`, in our case an `UnsizedStruct`. + let unsized_struct = cs::UnsizedStruct { + name: "A".into(), + sized_fields: vec![ + cs::SizedField::new("a", cs::Vector::new(cs::ScalarType::F32, cs::Len::X4)), + cs::SizedField::new("b", f32x3::layout_type_sized()), + ], + last_unsized: cs::RuntimeSizedArrayField::new("c", None, f32x1::layout_type_sized()), + }; + // And now we can get the `TypeLayout`. + let s_layout = TypeLayout::new_storage_layout_for(unsized_struct.clone()); + // For now `TypeLayout::::new_layout_for` only accepts sized types, + // however `TypeLayout::::new_layout_for_unchecked` allows to obtain the + // the uniform layout of an unsized cpu-shareable. Using that layout with wgsl as your + // target language will cause an error. + let u_layout = TypeLayout::new_uniform_layout_for(unsized_struct); + // This struct's field offsets are different for storage and uniform layout rules. The array + // has an alignment of 4 with storage alignment and an alignment of 16 with uniform alignment. + assert_ne!(s_layout, u_layout); + + #[derive(GpuLayout)] + struct B { + a: f32x4, + b: f32x3, + c: f32x1, + } + + // Sized structs require a builder to ensure it always contains at least one field. + let mut sized_struct = cs::SizedStruct::new("B", "b", f32x4::layout_type_sized()) + .extend("b", f32x3::layout_type_sized()) + .extend("c", f32x1::layout_type_sized()); + // Since this struct is sized we can use TypeLayout::::new_layout_for. + let u_layout = TypeLayout::new_uniform_layout_for(sized_struct.clone()); + let s_layout = TypeLayout::new_storage_layout_for(sized_struct); + // And this time they are equal, despite different layout rules. + assert_eq!(s_layout, u_layout); + // Using `TryFrom::try_from` we can check whether the storage type layout also follows + // uniform layout rules despite not being `TypeLayout`, + // which in this case will succeed, but if it doesn't we get a very nice error message about + // why the layout is not compatible with the uniform layout rules. + let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); + + // Let's replicate a more complex example with explicit field size and align. + #[derive(shame::GpuLayout)] + struct C { + a: f32x2, + #[size(16)] + b: f32x1, + #[align(16)] + c: f32x2, + } + + let mut sized_struct = cs::SizedStruct::new("C", "a", f32x3::layout_type_sized()) + .extend(FieldOptions::new("b", None, Some(16)), f32x3::layout_type_sized()) + .extend( + FieldOptions::new("c", Some(U32PowerOf2::_16), None), + f32x1::layout_type_sized(), + ); + let layout = TypeLayout::new_storage_layout_for(sized_struct); + assert!(layout.byte_align.as_u32() == 16); + + // Let's end on a pretty error message + let mut sized_struct = cs::SizedStruct::new("D", "a", f32x2::layout_type_sized()) + // This has align of 4 for storage and align of 16 for uniform. + .extend("b", Array::>::layout_type_sized()); + let s_layout = TypeLayout::new_storage_layout_for(sized_struct); + let result = TypeLayout::::try_from(&s_layout); + match result { + Err(e) => println!("This error is a showcase:\n{}", e), + Ok(u_layout) => println!("It unexpectedly worked, ohh no."), + } +} diff --git a/shame/src/common/po2.rs b/shame/src/common/po2.rs index 846e6c0..accf6a2 100644 --- a/shame/src/common/po2.rs +++ b/shame/src/common/po2.rs @@ -38,10 +38,11 @@ pub enum U32PowerOf2 { _2147483648, } -impl From for u32 { +impl U32PowerOf2 { + /// Returns the corresponding u32. #[rustfmt::skip] - fn from(value: U32PowerOf2) -> Self { - match value { + pub const fn as_u32(self) -> u32 { + match self { U32PowerOf2::_1 => 1_u32 , U32PowerOf2::_2 => 2_u32 , U32PowerOf2::_4 => 4_u32 , @@ -76,6 +77,18 @@ impl From for u32 { U32PowerOf2::_2147483648 => 2147483648_u32, } } + + /// Returns the corresponding u64. + pub const fn as_u64(self) -> u64 { self.as_u32() as u64 } +} + +impl From for u32 { + fn from(value: U32PowerOf2) -> Self { value.as_u32() } +} + +impl U32PowerOf2 { + /// Returns the maximum between `self` and `other`. + pub const fn max(self, other: Self) -> Self { if self as u32 > other as u32 { self } else { other } } } #[derive(Debug)] @@ -89,11 +102,10 @@ impl Display for NotAU32PowerOf2 { impl std::error::Error for NotAU32PowerOf2 {} -impl TryFrom for U32PowerOf2 { - type Error = NotAU32PowerOf2; - - fn try_from(value: u32) -> Result { - Ok(match value { +impl U32PowerOf2 { + /// Tries to convert a u32 to U32PowerOf2. + pub const fn try_from_u32(value: u32) -> Option { + Some(match value { 1 => U32PowerOf2::_1, 2 => U32PowerOf2::_2, 4 => U32PowerOf2::_4, @@ -126,11 +138,19 @@ impl TryFrom for U32PowerOf2 { 536870912 => U32PowerOf2::_536870912, 1073741824 => U32PowerOf2::_1073741824, 2147483648 => U32PowerOf2::_2147483648, - n => return Err(NotAU32PowerOf2(n)), + n => return None, }) } } +impl TryFrom for U32PowerOf2 { + type Error = NotAU32PowerOf2; + + fn try_from(value: u32) -> Result { + U32PowerOf2::try_from_u32(value).ok_or(NotAU32PowerOf2(value)) + } +} + impl From for u64 { fn from(value: U32PowerOf2) -> Self { u32::from(value) as u64 } } diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 30170bf..86d2f64 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -25,8 +25,16 @@ pub use crate::frontend::rust_types::struct_::BufferFields; pub use crate::frontend::rust_types::struct_::SizedFields; pub use crate::frontend::rust_types::type_layout::FieldLayout; pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; +pub use crate::frontend::rust_types::type_layout::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; -pub use crate::frontend::rust_types::type_layout::StructLayoutError; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedStruct; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryRepr; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryReprSized; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::LayoutType; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedType; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedOrArray; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::Repr; +pub use crate::frontend::rust_types::type_layout::cpu_shareable::StructFromPartsError; pub use crate::frontend::rust_types::type_layout::TypeLayout; pub use crate::frontend::rust_types::type_layout::TypeLayoutRules; pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index f392681..7e22a09 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -95,9 +95,11 @@ pub fn repr_c_struct_layout( Some(repr_c_align) => max_alignment.max(repr_c_align), None => max_alignment, }; + let struct_alignment = U32PowerOf2::try_from(struct_alignment as u32).unwrap(); let last_field_size = last_field_size.map(|s| s as u64); - let total_struct_size = last_field_size.map(|last_size| round_up(struct_alignment, last_field_offset + last_size)); + let total_struct_size = + last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); let mut fields = first_fields_with_offsets_and_sizes .iter() @@ -132,6 +134,7 @@ pub fn repr_c_struct_layout( name: struct_name.into(), fields, })), + None, )) } diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 5640f27..a963506 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,8 +2,10 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; +use crate::cpu_shareable; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::TypeLayout; +use crate::type_layout::cpu_shareable::array_stride; use crate::{ call_info, common::iterator_ext::try_collect, @@ -108,7 +110,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, ScalarType), + Fine(Len, cpu_shareable::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -238,7 +240,7 @@ impl VertexAttribFormat { #[allow(missing_docs)] pub fn type_in_shader(self) -> SizedType { match self { - VertexAttribFormat::Fine(l, t) => SizedType::Vector(l, t), + VertexAttribFormat::Fine(l, t) => SizedType::Vector(l, t.into()), VertexAttribFormat::Coarse(coarse) => coarse.decompressed_ty(), } } @@ -251,16 +253,16 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - stride_of_array_from_element_align_size(layout.align(), size) + array_stride(layout.byte_align(), size) }; use TypeLayoutSemantics as TLS; let attribs: Box<[Attrib]> = match &layout.kind { - TLS::Matrix(..) | TLS::Array(..) | TLS::Vector(_, ScalarType::Bool) => return None, - TLS::Vector(len, non_bool) => [Attrib { + TLS::Matrix(..) | TLS::Array(..) => return None, + TLS::Vector(v) => [Attrib { offset: 0, location: location_counter.next(), - format: VertexAttribFormat::Fine(*len, *non_bool), + format: VertexAttribFormat::Fine(v.len, v.scalar), }] .into(), TLS::PackedVector(packed_vector) => [Attrib { @@ -274,8 +276,7 @@ impl Attrib { offset: f.rel_byte_offset, location: location_counter.next(), format: match f.field.ty.kind { - TLS::Vector(_, ScalarType::Bool) => return None, - TLS::Vector(len, non_bool) => Some(VertexAttribFormat::Fine(len, non_bool)), + TLS::Vector(v) => Some(VertexAttribFormat::Fine(v.len, v.scalar)), TLS::PackedVector(packed_vector) => Some(VertexAttribFormat::Coarse(packed_vector)), TLS::Matrix(..) | TLS::Array(..) | TLS::Structure(..) => None, }?, diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index 50ad927..ee857c4 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -17,7 +17,8 @@ use crate::frontend::rust_types::{reference::AccessModeReadable, scalar_type::Sc use crate::ir::pipeline::StageMask; use crate::ir::recording::{Context, MemoryRegion}; use crate::ir::Type; -use crate::{self as shame, call_info, ir}; +use crate::type_layout::cpu_shareable::BinaryReprSized; +use crate::{self as shame, call_info, ir, GpuLayout}; use std::borrow::Borrow; use std::marker::PhantomData; @@ -97,7 +98,7 @@ where impl Buffer where - T: GpuStore + NoHandles + NoAtomics + NoBools, + T: GpuStore + NoHandles + NoAtomics + NoBools + GpuLayout, AS: BufferAddressSpace, { #[track_caller] @@ -114,7 +115,7 @@ where impl BufferRef where - T: GpuStore + NoHandles + NoBools, + T: GpuStore + NoHandles + NoBools + GpuLayout, AS: BufferAddressSpace, AM: AccessModeReadable, { @@ -294,7 +295,7 @@ impl BufferRefInner #[rustfmt::skip] impl Binding for Buffer where - T: GpuSized + T: GpuSized+ GpuLayout { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] @@ -316,9 +317,9 @@ fn store_type_from_impl_category(category: GpuStoreImplCategory) -> ir::StoreTyp } #[rustfmt::skip] impl -Binding for Buffer, AS, DYN_OFFSET> -where - T: GpuType + GpuSized +Binding for Buffer, AS, DYN_OFFSET> +where + T: GpuType + GpuSized + GpuLayout + BinaryReprSized { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] @@ -464,7 +465,7 @@ where /// /// // field access returns references /// let world: sm::Ref = buffer.world; -/// +/// /// // get fields via `.get()` /// let matrix: f32x4x4 = buffer.world.get(); /// @@ -488,7 +489,7 @@ where pub(crate) inner: BufferRefInner, } -#[rustfmt::skip] impl +#[rustfmt::skip] impl Binding for BufferRef where AS: BufferAddressSpace + SupportsAccess, @@ -497,7 +498,7 @@ where fn binding_type() -> BindingType { BufferRefInner::::binding_type(DYN_OFFSET) } #[track_caller] fn new_binding(args: Result) -> Self { BufferRef::new(args) } - + fn store_ty() -> ir::StoreType { store_type_from_impl_category(T::impl_category()) } diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index 67a8511..e3e20af 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -107,7 +107,7 @@ impl VertexBuffer<'_, T> { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check).into_plain(); let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); @@ -454,13 +454,13 @@ impl BindingIter<'_> { /// let texarr: sm::TextureArray = bind_group.next(); /// let texarr: sm::TextureArray, 4> = bind_group.next(); /// ``` - /// --- + /// --- /// ## storage textures /// ``` /// let texsto: sm::StorageTexture = bind_group.next(); /// let texsto: sm::StorageTexture = bind_group.next(); /// ``` - /// --- + /// --- /// ## Arrays of storage textures /// ``` /// let texstoarr: sm::StorageTextureArray = bind_group.next(); @@ -558,7 +558,7 @@ impl PushConstants<'_> { #[track_caller] pub fn get(self) -> T where - T: GpuStore + GpuSized + NoAtomics + NoBools, + T: GpuStore + GpuSized + NoAtomics + NoBools + GpuLayout, { let _caller_scope = Context::call_info_scope(); diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 853e6e1..644d10a 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -13,6 +13,7 @@ use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; use crate::common::small_vec::SmallVec; +use crate::cpu_shareable::{BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; use crate::frontend::any::InvalidReason; @@ -24,7 +25,7 @@ use crate::frontend::rust_types::vec::vec; use crate::ir::ir_type::stride_of_array_from_element_align_size; use crate::ir::pipeline::StageMask; use crate::ir::recording::Context; -use crate::{call_info, for_count, ir}; +use crate::{call_info, cpu_shareable, for_count, ir}; use std::borrow::Cow; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -155,19 +156,30 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} +impl BinaryReprSized for Array> { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::SizedArray::new(T::layout_type_sized(), Size::::nonzero()).into() + } +} +impl BinaryRepr for Array { + fn layout_type() -> cpu_shareable::LayoutType { + match N::LEN { + Some(n) => cpu_shareable::SizedArray::new(T::layout_type_sized(), n).into(), + None => cpu_shareable::RuntimeSizedArray::new(T::layout_type_sized()).into(), + } + } +} impl ToGpuType for Array { type Gpu = Self; - - fn to_gpu(&self) -> Self::Gpu { self.clone() } fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { Some(self) } } -impl GpuLayout for Array { - fn gpu_layout() -> TypeLayout { TypeLayout::from_array(TypeLayoutRules::Wgsl, &T::sized_ty(), N::LEN) } +impl GpuLayout for Array { + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -187,14 +199,15 @@ impl GpuLayout for Array { name.into(), TypeLayout::new( N::LEN.map(|n| n.get() as u64 * t_cpu_size), - t_cpu_layout.align(), + t_cpu_layout.byte_align(), TypeLayoutSemantics::Array( Rc::new(ElementLayout { - byte_stride: stride_of_array_from_element_align_size(t_cpu_layout.align(), t_cpu_size), + byte_stride: cpu_shareable::array_stride(t_cpu_layout.byte_align(), t_cpu_size), ty: t_cpu_layout, }), N::LEN.map(NonZeroU32::get), ), + None, ), ); diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index e391b6b..cb239b8 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -6,7 +6,7 @@ use super::{ mem::{AddressSpace, AddressSpaceAtomic}, reference::{AccessMode, AccessModeReadable, ReadWrite}, scalar_type::{ScalarType, ScalarTypeInteger}, - type_layout::{TypeLayout, TypeLayoutRules}, + type_layout::{cpu_shareable::BinaryReprSized, TypeLayout, TypeLayoutRules}, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -14,7 +14,10 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::frontend::rust_types::reference::Ref; +use crate::{ + cpu_shareable::{self, BinaryRepr}, + frontend::rust_types::reference::Ref, +}; use crate::{ boolx1, frontend::{ @@ -129,8 +132,20 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } +impl BinaryReprSized for Atomic { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::Atomic { + scalar: T::SCALAR_TYPE_INTEGER, + } + .into() + } +} +impl BinaryRepr for Atomic { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + impl GpuLayout for Atomic { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 96e9373..0b10946 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,6 +1,7 @@ use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; +use crate::cpu_shareable::{BinaryRepr, BinaryReprSized, Repr}; use crate::frontend::any::render_io::{ Attrib, VertexBufferLookupIndex, Location, VertexAttribFormat, VertexBufferLayout, VertexLayoutError, }; @@ -21,9 +22,11 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; +use super::type_layout::constraint::TypeConstraint; +use super::type_layout::cpu_shareable::{self, array_stride, Vector}; use super::type_layout::{ - ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, TypeLayoutRules, - TypeLayoutSemantics, + self, constraint, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, + TypeLayoutRules, TypeLayoutSemantics, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -114,7 +117,7 @@ use std::rc::Rc; /// # Layout comparison of different types /// /// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared -/// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` +/// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` /// ``` /// use shame as sm; /// use sm::{ GpuLayout, CpuLayout }; @@ -135,14 +138,14 @@ use std::rc::Rc; /// [`Texture`]: crate::Texture /// [`StorageTexture`]: crate::StorageTexture /// -pub trait GpuLayout { +pub trait GpuLayout: BinaryRepr { /// returns a [`TypeLayout`] object that can be used to inspect the layout /// of a type on the gpu. /// /// # Layout comparison of different types /// /// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared - /// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` + /// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` /// ``` /// use shame as sm; /// use sm::{ GpuLayout, CpuLayout }; @@ -156,7 +159,10 @@ pub trait GpuLayout { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` - fn gpu_layout() -> TypeLayout; + fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layout_type(), Self::gpu_repr()) } + + /// TODO(chronicl) docs + fn gpu_repr() -> Repr; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a /// corresponding Cpu type to the Gpu type that the derive macro is used on. @@ -201,15 +207,15 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, - cpu_layout: &TypeLayout, - gpu_layout: &TypeLayout, + cpu_layout: &TypeLayout, + gpu_layout: &TypeLayout, skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { - TypeLayout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) + type_layout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { if skip_stride_check { @@ -222,8 +228,8 @@ pub(crate) fn check_layout_push_error( name: gpu_layout.short_name(), }), (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = stride_of_array_from_element_align_size(cpu_layout.align(), cpu_size); - let gpu_stride = stride_of_array_from_element_align_size(gpu_layout.align(), gpu_size); + let cpu_stride = array_stride(cpu_layout.byte_align(), cpu_size); + let gpu_stride = array_stride(gpu_layout.byte_align(), gpu_size); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { @@ -348,6 +354,7 @@ pub(crate) fn from_single_any(mut anys: impl Iterator) -> Any { pub trait VertexLayout: GpuLayout + FromAnys {} impl VertexLayout for T {} +// TODO(chronicl) uncomment // #[derive(GpuLayout)] // TODO(release) remove this type. This is an example impl for figuring out how the derive macro should work #[derive(Clone)] @@ -533,28 +540,15 @@ where } } +impl BinaryReprSized for GpuT { + fn layout_type_sized() -> cpu_shareable::SizedType { todo!() } +} +impl BinaryRepr for GpuT { + fn layout_type() -> cpu_shareable::LayoutType { todo!() } +} + impl GpuLayout for GpuT { - fn gpu_layout() -> TypeLayout { - use crate::__private::proc_macro_reexports as rx; - // compiler_error! if the struct has zero fields! - let is_packed = false; - let result = rx::TypeLayout::struct_from_parts( - rx::TypeLayoutRules::Wgsl, - is_packed, - std::stringify!(GpuT).into(), - [FieldLayout { - name: unreachable!(), - custom_min_size: unreachable!(), - custom_min_align: unreachable!(), - ty: as rx::GpuLayout>::gpu_layout(), - }] - .into_iter(), - ); - match result { - Ok(layout) => layout, - Err(e @ rx::StructLayoutError::UnsizedFieldMustBeLast { .. }) => unreachable!(), - } - } + fn gpu_repr() -> Repr { todo!() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -701,7 +695,10 @@ where impl CpuLayout for f32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::F32, + cpu_shareable::Len::X1, + ))) } // fn gpu_type_layout() -> Option> { // Some(Ok(vec::::gpu_layout())) @@ -710,29 +707,29 @@ impl CpuLayout for f32 { impl CpuLayout for f64 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::F64, + cpu_shareable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } impl CpuLayout for u32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::U32, + cpu_shareable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } impl CpuLayout for i32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(ir::Len::X1, Self::SCALAR_TYPE)) + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + cpu_shareable::ScalarType::I32, + cpu_shareable::Len::X1, + ))) } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } } /// (no documentation yet) @@ -760,7 +757,7 @@ impl CpuAligned for [T] { impl CpuLayout for [T; N] { fn cpu_layout() -> TypeLayout { - let align = ::alignment() as u64; + let align = U32PowerOf2::try_from(::alignment() as u32).unwrap(); TypeLayout::new( Some(std::mem::size_of::() as u64), @@ -772,6 +769,7 @@ impl CpuLayout for [T; N] { }), Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), ), + None, ) } @@ -803,7 +801,7 @@ impl CpuLayout for [T; N] { impl CpuLayout for [T] { fn cpu_layout() -> TypeLayout { - let align = ::alignment() as u64; + let align = U32PowerOf2::try_from(::alignment() as u32).unwrap(); TypeLayout::new( None, @@ -815,6 +813,7 @@ impl CpuLayout for [T] { }), None, ), + None, ) } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 08c72ab..ded204c 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -7,7 +7,10 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{ScalarType, ScalarTypeFp}, - type_layout::{TypeLayout, TypeLayoutRules}, + type_layout::{ + cpu_shareable::{self, BinaryReprSized}, + TypeLayout, TypeLayoutRules, + }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -15,7 +18,7 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; +use crate::{cpu_shareable::BinaryRepr, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; use crate::{ call_info, frontend::{ @@ -50,8 +53,22 @@ impl Default for mat { } } +impl BinaryReprSized for mat { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::Matrix { + columns: C::LEN2, + rows: R::LEN2, + scalar: T::SCALAR_TYPE_FP, + } + .into() + } +} +impl BinaryRepr for mat { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + impl GpuLayout for mat { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } @@ -181,7 +198,7 @@ impl mat { /// sm::vec!(2.0, 5.0) // row 3 /// ]) /// - /// let m: f32x3x2 = sm::mat::new([ + /// let m: f32x3x2 = sm::mat::new([ /// 0.0, 3.0 // column 0, becomes row 0 /// 1.0, 4.0 // column 1, becomes row 1 /// 2.0, 5.0 // column 2, becomes row 2 @@ -235,7 +252,7 @@ impl mat { /// sm::vec!(2.0, 5.0) // row 3 /// ]) /// - /// let m: f32x3x2 = sm::mat::new([ + /// let m: f32x3x2 = sm::mat::new([ /// 0.0, 3.0 // column 0, becomes row 0 /// 1.0, 4.0 // column 1, becomes row 1 /// 2.0, 5.0 // column 2, becomes row 2 @@ -496,7 +513,7 @@ impl mat { /// sm::vec!(2.0, 5.0) // row 3 /// ]) /// - /// let m: f32x3x2 = sm::mat::new([ + /// let m: f32x3x2 = sm::mat::new([ /// 0.0, 3.0 // column 0, becomes row 0 /// 1.0, 4.0 // column 1, becomes row 1 /// 2.0, 5.0 // column 2, becomes row 2 diff --git a/shame/src/frontend/rust_types/mod.rs b/shame/src/frontend/rust_types/mod.rs index 8a79162..5d6da72 100644 --- a/shame/src/frontend/rust_types/mod.rs +++ b/shame/src/frontend/rust_types/mod.rs @@ -51,7 +51,7 @@ pub mod vec_range_traits; //[old-doc] - `StorageTexture<…>` /// (no documentation yet) /// -pub trait GpuType: ToGpuType + From + AsAny + Clone + GpuLayout { +pub trait GpuType: ToGpuType + From + AsAny + Clone { /// (no documentation yet) #[doc(hidden)] // returns a type from the `any` api fn ty() -> ir::Type; diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 7331f5f..148c98e 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -7,6 +7,7 @@ use std::{ use crate::{ any::{AsAny, DataPackingFn}, common::floating_point::f16, + cpu_shareable::{BinaryRepr, BinaryReprSized}, f32x2, f32x4, i32x4, u32x1, u32x4, }; use crate::frontend::rust_types::len::{x1, x2, x3, x4}; @@ -22,7 +23,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, + type_layout::{cpu_shareable, type_layout_internal, TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -104,7 +105,7 @@ impl PackedVec { } } -fn get_type_description() -> PackedVector { +pub(crate) fn get_type_description() -> PackedVector { PackedVector { len: L::LEN_EVEN, bits_per_component: T::BITS_PER_COMPONENT, @@ -130,22 +131,29 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} +impl BinaryReprSized for PackedVec { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::PackedVector { + scalar_type: T::SCALAR_TYPE, + bits_per_component: T::BITS_PER_COMPONENT, + len: L::LEN_EVEN, + } + .into() + } +} +impl BinaryRepr for PackedVec { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} impl GpuLayout for PackedVec { - fn gpu_layout() -> TypeLayout { - let packed_vec = get_type_description::(); - TypeLayout::new( - Some(u8::from(packed_vec.byte_size()) as u64), - packed_vec.align(), - TypeLayoutSemantics::PackedVector(get_type_description::()), - ) - } + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty = Self::sized_ty_equivalent(); let name = sized_ty.to_string().into(); - let layout = TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &sized_ty); - Some(Ok((name, layout))) + let sized_ty: cpu_shareable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); + let layout = TypeLayout::new_storage_layout_for(sized_ty); + Some(Ok((name, layout.into()))) } } @@ -162,7 +170,7 @@ impl From for PackedVec { let inner = Context::try_with(call_info!(), |ctx| { let err = |ty| { ctx.push_error_get_invalid_any( - FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout()).into(), + FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout().into()).into(), ) }; match any.ty() { diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index f5615dc..4fec66f 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,4 +1,5 @@ use crate::common::small_vec::SmallVec; +use crate::cpu_shareable::{self, BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; use crate::frontend::encoding::buffer::BufferRefInner; @@ -22,7 +23,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::TypeLayout; +use super::type_layout::{TypeLayout, TypeLayoutRules}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -134,8 +135,19 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl GpuLayout for Struct { - fn gpu_layout() -> TypeLayout { T::gpu_layout() } +impl BinaryReprSized for Struct { + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::SizedStruct::try_from(T::get_sizedstruct_type()) + .expect("no bools") + .into() + } +} +impl BinaryRepr for Struct { + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + +impl GpuLayout for Struct { + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout.rs b/shame/src/frontend/rust_types/type_layout.rs deleted file mode 100644 index 14811ce..0000000 --- a/shame/src/frontend/rust_types/type_layout.rs +++ /dev/null @@ -1,906 +0,0 @@ -use std::{ - fmt::{Debug, Display, Write}, - num::NonZeroU32, - ops::Deref, - rc::Rc, -}; - -use crate::{ - call_info, - common::{ - ignore_eq::{IgnoreInEqOrdHash, InEqOrd}, - prettify::set_color, - }, - ir::{ - self, - ir_type::{ - align_of_array, byte_size_of_array, round_up, stride_of_array, AlignedType, CanonName, LenEven, - PackedVectorByteSize, ScalarTypeFp, ScalarTypeInteger, - }, - recording::Context, - Len, SizedType, Type, - }, -}; -use thiserror::Error; - -/// The type contained in the bytes of a `TypeLayout` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TypeLayoutSemantics { - /// `vec` - Vector(ir::Len, ir::ScalarType), - /// special compressed vectors for vertex attribute types - /// - /// see the [`crate::packed`] module - PackedVector(ir::PackedVector), - /// `mat`, first `Len2` is cols, 2nd `Len2` is rows - Matrix(ir::Len2, ir::Len2, ScalarTypeFp), - /// `Array` and `Array>` - Array(Rc, Option), // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. - /// structures which may be empty and may have an unsized last field - Structure(Rc), -} - -/// The memory layout of a type. -/// -/// This models only the layout, not other characteristics of the types. -/// For example an `Atomic>` is treated like a regular `vec` layout wise. -/// -/// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question -/// "do these two types have the same layout" so that uploading a type to the gpu -/// will result in no memory errors. -/// -/// a layout comparison looks like this: -/// ``` -/// assert!(f32::cpu_layout() == vec::gpu_layout()); -/// // or, more explicitly -/// assert_eq!( -/// ::cpu_layout(), -/// as GpuLayout>::gpu_layout(), -/// ); -/// ``` -/// -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct TypeLayout { - /// size in bytes (Some), or unsized (None) - pub byte_size: Option, - /// the byte alignment - /// - /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) - pub byte_align: IgnoreInEqOrdHash, - /// the type contained in the bytes of this type layout - pub kind: TypeLayoutSemantics, -} - -impl TypeLayout { - pub(crate) fn new(byte_size: Option, byte_align: u64, kind: TypeLayoutSemantics) -> Self { - Self { - byte_size, - byte_align: byte_align.into(), - kind, - } - } - - pub(crate) fn from_rust_sized(kind: TypeLayoutSemantics) -> Self { - Self::new(Some(size_of::() as u64), align_of::() as u64, kind) - } - - fn first_line_of_display_with_ellipsis(&self) -> String { - let string = format!("{}", self); - string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) - } - - /// a short name for this `TypeLayout`, useful for printing inline - pub fn short_name(&self) -> String { - match &self.kind { - TypeLayoutSemantics::Vector { .. } | - TypeLayoutSemantics::PackedVector { .. } | - TypeLayoutSemantics::Matrix { .. } => format!("{}", self), - TypeLayoutSemantics::Array(element_layout, n) => match n { - Some(n) => format!("array<{}, {n}>", element_layout.ty.short_name()), - None => format!("array<{}, runtime-sized>", element_layout.ty.short_name()), - }, - TypeLayoutSemantics::Structure(s) => s.name.to_string(), - } - } -} - -/// a sized or unsized struct type with 0 or more fields -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StructLayout { - pub name: IgnoreInEqOrdHash, - pub fields: Vec, -} - -impl StructLayout { - /// this exists, because if in the future a refactor happens that separates - /// fields into sized and unsized fields, the intention of this function is - /// clear - fn all_fields(&self) -> &[FieldLayoutWithOffset] { &self.fields } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FieldLayoutWithOffset { - pub field: FieldLayout, - pub rel_byte_offset: u64, // this being relative is used in TypeLayout::byte_size -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ElementLayout { - pub byte_stride: u64, - pub ty: TypeLayout, -} - -/// the layout rules used when calculating the byte offsets and alignment of a type -#[derive(Debug, Clone, Copy)] -pub enum TypeLayoutRules { - /// wgsl type layout rules, see https://www.w3.org/TR/WGSL/#memory-layouts - Wgsl, - // reprC, - // Std140, - // Std430, - // Scalar, -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub enum TypeLayoutError { - #[error("An array cannot contain elements of an unsized type {elements}")] - ArrayOfUnsizedElements { elements: TypeLayout }, - #[error("the type `{0}` has no defined {1:?} layout in shaders")] - LayoutUndefined(Type, TypeLayoutRules), - #[error("in `{parent_name}` at field `{field_name}`: {error}")] - AtField { - parent_name: CanonName, - field_name: CanonName, - error: Rc, - }, - #[error("in element of array: {0}")] - InArrayElement(Rc), -} - -#[allow(missing_docs)] // TODO(docs) low priority docs, add after release -impl TypeLayout { - pub fn from_ty(rules: TypeLayoutRules, ty: &ir::Type) -> Result { - match ty { - Type::Unit | Type::Ptr(_, _, _) | Type::Ref(_, _, _) => { - Err(TypeLayoutError::LayoutUndefined(ty.clone(), rules)) - } - Type::Store(ty) => Self::from_store_ty(rules, ty), - } - } - - pub fn from_store_ty(rules: TypeLayoutRules, ty: &ir::StoreType) -> Result { - match ty { - ir::StoreType::Sized(sized) => Ok(TypeLayout::from_sized_ty(rules, sized)), - ir::StoreType::Handle(handle) => Err(TypeLayoutError::LayoutUndefined(ir::Type::Store(ty.clone()), rules)), - ir::StoreType::RuntimeSizedArray(element) => Ok(Self::from_array(rules, element, None)), - ir::StoreType::BufferBlock(s) => Ok(Self::from_struct(rules, s)), - } - } - - pub fn from_sized_ty(rules: TypeLayoutRules, ty: &ir::SizedType) -> TypeLayout { - pub use TypeLayoutSemantics as Sem; - let size = ty.byte_size(); - let align = ty.align(); - match ty { - ir::SizedType::Vector(l, t) => - // we treat bool as a type that has a layout to allow for an - // `Eq` operator on `TypeLayout` that behaves intuitively. - // The layout of `bool`s is not actually observable in any part of the api. - { - TypeLayout::new(Some(size), align, Sem::Vector(*l, *t)) - } - ir::SizedType::Matrix(c, r, t) => TypeLayout::new(Some(size), align, Sem::Matrix(*c, *r, *t)), - ir::SizedType::Array(sized, l) => Self::from_array(rules, sized, Some(*l)), - ir::SizedType::Atomic(t) => Self::from_sized_ty(rules, &ir::SizedType::Vector(ir::Len::X1, (*t).into())), - ir::SizedType::Structure(s) => Self::from_struct(rules, s), - } - } - - pub fn from_array(rules: TypeLayoutRules, element: &ir::SizedType, len: Option) -> TypeLayout { - TypeLayout::new( - len.map(|n| byte_size_of_array(element, n)), - align_of_array(element), - TypeLayoutSemantics::Array( - Rc::new(ElementLayout { - byte_stride: match rules { - TypeLayoutRules::Wgsl => stride_of_array(element), - }, - ty: Self::from_sized_ty(rules, element), - }), - len.map(NonZeroU32::get), - ), - ) - } - - pub fn from_struct(rules: TypeLayoutRules, s: &ir::Struct) -> TypeLayout { - let (size, align, struct_layout) = StructLayout::from_ir_struct(rules, s); - TypeLayout::new(size, align, TypeLayoutSemantics::Structure(Rc::new(struct_layout))) - } - - pub fn struct_from_parts( - rules: TypeLayoutRules, - packed: bool, - name: CanonName, - fields: impl ExactSizeIterator, - ) -> Result { - let (byte_size, byte_align, struct_) = StructLayout::new(rules, packed, name, fields)?; - let layout = TypeLayout::new(byte_size, byte_align, TypeLayoutSemantics::Structure(Rc::new(struct_))); - Ok(layout) - } - - pub fn from_aligned_type(rules: TypeLayoutRules, ty: &AlignedType) -> TypeLayout { - match ty { - AlignedType::Sized(sized) => Self::from_sized_ty(rules, sized), - AlignedType::RuntimeSizedArray(element) => Self::from_array(rules, element, None), - } - } - - pub(crate) fn writeln(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { - self.write(indent, colored, f)?; - writeln!(f) - } - - //TODO(low prio) try to figure out a cleaner way of writing these. - pub(crate) fn write(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { - let tab = " "; - let use_256_color_mode = false; - let color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - use TypeLayoutSemantics as Sem; - - match &self.kind { - Sem::Vector(l, t) => match l { - Len::X1 => write!(f, "{t}")?, - l => write!(f, "{t}x{}", u64::from(*l))?, - }, - Sem::PackedVector(c) => write!(f, "{}", c)?, - Sem::Matrix(c, r, t) => write!(f, "{}", ir::SizedType::Matrix(*c, *r, *t))?, - Sem::Array(t, n) => { - let stride = t.byte_stride; - write!(f, "array<")?; - t.ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(n) = n { - write!(f, ", {n}")?; - } - write!(f, "> stride={stride}")?; - } - Sem::Structure(s) => { - writeln!(f, "struct {} {{", s.name)?; - { - let indent = indent.to_string() + tab; - for field in &s.fields { - let offset = field.rel_byte_offset; - let field = &field.field; - write!(f, "{indent}{offset:3} {}: ", field.name)?; - field.ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(size) = field.ty.byte_size { - let size = size.max(field.custom_min_size.unwrap_or(0)); - write!(f, " size={size}")?; - } else { - write!(f, " size=?")?; - } - writeln!(f, ",")?; - } - } - write!(f, "{indent}}}")?; - write!(f, " align={}", self.byte_align)?; - if let Some(size) = self.byte_size { - write!(f, " size={size}")?; - } else { - write!(f, " size=?")?; - } - } - }; - Ok(()) - } - - pub fn align(&self) -> u64 { *self.byte_align } - - pub fn byte_size(&self) -> Option { self.byte_size } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FieldLayout { - pub name: CanonName, - pub custom_min_size: IgnoreInEqOrdHash>, // whether size/align is custom doesn't matter for the layout equality. - pub custom_min_align: IgnoreInEqOrdHash>, - pub ty: TypeLayout, -} - -impl FieldLayout { - fn byte_size(&self) -> Option { - self.ty - .byte_size() - .map(|byte_size| byte_size.max(self.custom_min_size.unwrap_or(0))) - } - fn align(&self) -> u64 { self.ty.align().max(self.custom_min_align.map(u64::from).unwrap_or(1)) } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum StructLayoutError { - #[error( - "field #{unsized_field_index} in struct `{struct_name}` with {num_fields} is unsized. Only the last field may be unsized." - )] - UnsizedFieldMustBeLast { - struct_name: CanonName, - unsized_field_index: usize, - num_fields: usize, - }, -} - -impl StructLayout { - /// returns a `(byte_size, byte_alignment, struct_layout)` tuple or an error - /// - /// this was created for the `#[derive(GpuLayout)]` macro to support the - /// non-GpuType `PackedVec` for gpu_repr(packed) and non-packed. - /// - // TODO(low prio) find a way to merge all struct layout calculation functions in this codebase. This is very redundand. - pub(crate) fn new( - rules: TypeLayoutRules, - packed: bool, - name: CanonName, - fields: impl ExactSizeIterator, - ) -> Result<(Option, u64, StructLayout), StructLayoutError> { - let mut total_byte_size = None; - let mut total_align = 1; - let num_fields = fields.len(); - let struct_layout = StructLayout { - name: name.clone().into(), - fields: { - let mut offset_so_far = 0; - let mut fields_with_offset = Vec::new(); - for (i, field) in fields.enumerate() { - let is_last = i + 1 == num_fields; - fields_with_offset.push(FieldLayoutWithOffset { - field: field.clone(), - rel_byte_offset: match rules { - TypeLayoutRules::Wgsl => { - let field_offset = match (packed, *field.custom_min_align) { - (true, None) => offset_so_far, - (true, Some(custom_align)) => round_up(custom_align, offset_so_far), - (false, _) => round_up(field.align(), offset_so_far), - }; - match (field.byte_size(), is_last) { - (Some(field_size), _) => { - offset_so_far = field_offset + field_size; - Ok(()) - } - (None, true) => Ok(()), - (None, false) => Err(StructLayoutError::UnsizedFieldMustBeLast { - struct_name: name.clone(), - unsized_field_index: i, - num_fields, - }), - }?; - field_offset - } - }, - }); - total_align = total_align.max(field.align()); - if is_last { - // wgsl spec: - // roundUp(AlignOf(S), justPastLastMember) - // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) - - // if the last field size is None (= unsized), just_past_last is None (= unsized) - let just_past_last = field.byte_size().map(|_| offset_so_far); - total_byte_size = just_past_last.map(|just_past_last| round_up(total_align, just_past_last)); - } - } - fields_with_offset - }, - }; - Ok((total_byte_size, total_align, struct_layout)) - } - - /// returns a `(byte_size, byte_alignment, struct_layout)` tuple - #[doc(hidden)] - pub fn from_ir_struct(rules: TypeLayoutRules, s: &ir::Struct) -> (Option, u64, StructLayout) { - let mut total_byte_size = None; - let struct_layout = StructLayout { - name: s.name().clone().into(), - fields: { - let mut offset = 0; - let mut fields = Vec::new(); - for field in s.sized_fields() { - fields.push(FieldLayoutWithOffset { - field: FieldLayout { - name: field.name.clone(), - ty: TypeLayout::from_sized_ty(rules, &field.ty), - custom_min_size: field.custom_min_size.into(), - custom_min_align: field.custom_min_align.map(u64::from).into(), - }, - rel_byte_offset: match rules { - TypeLayoutRules::Wgsl => { - let rel_byte_offset = round_up(field.align(), offset); - offset = rel_byte_offset + field.byte_size(); - rel_byte_offset - } - }, - }) - } - if let Some(unsized_array) = s.last_unsized_field() { - fields.push(FieldLayoutWithOffset { - field: FieldLayout { - name: unsized_array.name.clone(), - custom_min_align: unsized_array.custom_min_align.map(u64::from).into(), - custom_min_size: None.into(), - ty: TypeLayout::from_array(rules, &unsized_array.element_ty, None), - }, - rel_byte_offset: round_up(unsized_array.align(), offset), - }) - } else { - total_byte_size = Some(s.min_byte_size()); - } - fields - }, - }; - (total_byte_size, s.align(), struct_layout) - } -} - -impl Display for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); - self.write("", colored, f) - } -} - -impl Debug for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } -} - -#[derive(Clone)] -pub struct LayoutMismatch { - /// 2 (name, layout) pairs - layouts: [(String, TypeLayout); 2], - colored_error: bool, -} - - -impl std::fmt::Debug for LayoutMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self) // use Display - } -} - -impl LayoutMismatch { - fn pad_width(name_a: &str, name_b: &str) -> usize { name_a.chars().count().max(name_b.chars().count()) + SEP.len() } -} - -impl Display for LayoutMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let colored = self.colored_error; - let [(a_name, a), (b_name, b)] = &self.layouts; - write!(f, "{:width$}", ' ', width = Self::pad_width(a_name, b_name))?; - let layouts = [(a_name.as_str(), a), (b_name.as_str(), b)]; - match LayoutMismatch::write("", layouts, colored, f) { - Err(MismatchWasFound) => Ok(()), - Ok(KeepWriting) => { - writeln!( - f, - "" - )?; - writeln!(f, "the full type layouts in question are:")?; - for (name, layout) in layouts { - writeln!(f, "`{}`:", name)?; - writeln!(f, "{}", layout)?; - } - Ok(()) - } - } - } -} - -/// layout mismatch diff name separator -/// used in the display impl of LayoutMismatch to show the actual place where the layouts mismatch -/// ``` -/// layout mismatch: -/// cpu{SEP} f32 -/// gpu{SEP} i32 -/// ``` -const SEP: &str = ": "; - -/// whether the mismatching part of the TypeLayouts in a LayoutMismatch was already expressed via writes. -/// indicates that the `write` function should stop writing. -pub(crate) struct MismatchWasFound; -pub(crate) struct KeepWriting; - -impl LayoutMismatch { - //TODO(low prio) try to figure out a cleaner way of writing these. - - /// this function uses the `Err(MismatchWasFound)` to halt traversing the typelayout. - /// It does not constitute an error of this function, it is just so the ? operator can be used to propagate the abort. - #[allow(clippy::needless_return)] - pub(crate) fn write( - indent: &str, - layouts: [(&str, &TypeLayout); 2], - colored: bool, - f: &mut W, - ) -> Result { - let tab = " "; - let [(a_name, a), (b_name, b)] = layouts; - - if a == b { - a.write(indent, colored, f); - return Ok(KeepWriting); - } - - let use_256_color_mode = false; - let hex_color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let color_reset = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - let color_a_hex = "#DF5853"; - let color_b_hex = "#9A639C"; - let color_a = |f| hex_color(f, color_a_hex); - let color_b = |f| hex_color(f, color_b_hex); - - let pad_width = Self::pad_width(a_name, b_name); - - use TypeLayoutSemantics as S; - match (&a.kind, &b.kind) { - (S::Structure(sa), S::Structure(sb)) => { - let max_fields = sa.all_fields().len().max(sb.all_fields().len()); - { - write!(f, "struct "); - hex_color(f, color_a_hex); - write!(f, "{}", sa.name); - color_reset(f); - write!(f, " / "); - hex_color(f, color_b_hex); - write!(f, "{}", sb.name); - color_reset(f); - writeln!(f, " {{"); - } - - let mut sa_fields = sa.all_fields().iter(); - let mut sb_fields = sb.all_fields().iter(); - - loop { - //TODO(low prio) get a hold of the code duplication here - match (sa_fields.next(), sb_fields.next()) { - (Some(a_field), Some(b_field)) => { - let offsets_match = a_field.rel_byte_offset == b_field.rel_byte_offset; - let types_match = a_field.field.ty == b_field.field.ty; - if !offsets_match && types_match { - // only write this mismatch if the types are also the same, otherwise display the detailed type mismatch further below - let a_ty_string = a_field.field.ty.first_line_of_display_with_ellipsis(); - let b_ty_string = b_field.field.ty.first_line_of_display_with_ellipsis(); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", - a_field.rel_byte_offset, - a_field.field.name, - a_field.field.align() - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", - b_field.rel_byte_offset, - b_field.field.name, - b_field.field.align() - ); - color_reset(f); - writeln!( - f, - "field offset is different on {a_name} ({}) and {b_name} ({}).", - a_field.rel_byte_offset, b_field.rel_byte_offset - ); - return Err(MismatchWasFound); - } - let offset = a_field.rel_byte_offset; - let a_field = &a_field.field; - let b_field = &b_field.field; - let field = &a_field; - - if offsets_match { - write!(f, "{:width$}{indent}{offset:3} ", ' ', width = pad_width); - } else { - write!(f, "{:width$}{indent} ? ", ' ', width = pad_width); - } - if a_field.name != b_field.name { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent} {}: …", a_field.name); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent} {}: …", b_field.name); - color_reset(f); - writeln!( - f, - "identifier mismatch, either\nfield '{}' is missing on {a_name}, or\nfield '{}' is missing on {b_name}.", - b_field.name, a_field.name - ); - return Err(MismatchWasFound); - } - write!(f, "{}: ", field.name); - if a_field.ty != b_field.ty { - Self::write( - &format!("{indent}{tab}"), - [(a_name, &a_field.ty), (b_name, &b_field.ty)], - colored, - f, - )?; - return Err(MismatchWasFound); - } - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - if a_field.byte_size() != b_field.byte_size() { - writeln!(f); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent} size={}", - a_field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent} size={}", - b_field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - color_reset(f); - return Err(MismatchWasFound); - } - - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - } - (Some(a_field), None) => { - let offset = a_field.rel_byte_offset; - let a_field = &a_field.field; - let field = &a_field; - color_a(f); - write!(f, "{a_name}{SEP}{indent}{offset:3} "); - write!(f, "{}: ", field.name); - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}", a_field.name); - color_reset(f); - return Err(MismatchWasFound); - } - (None, Some(b_field)) => { - let offset = b_field.rel_byte_offset; - let b_field = &b_field.field; - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}", b_field.name); - let field = &b_field; - color_b(f); - write!(f, "{b_name}{SEP}{indent}{offset:3} "); - write!(f, "{}: ", field.name); - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - color_reset(f); - return Err(MismatchWasFound); - } - (None, None) => break, - } - } - - write!(f, "{:width$}{indent}}}", ' ', width = pad_width); - let align_matches = a.align() == b.align(); - let size_matches = a.byte_size() == b.byte_size(); - if !align_matches && size_matches { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}align={}", a.align()); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}align={}", b.align()); - color_reset(f); - return Err(MismatchWasFound); - } else { - match align_matches { - true => write!(f, " align={}", a.align()), - false => write!(f, " align=?"), - }; - } - if !size_matches { - writeln!(f); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent}size={}", - a.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent}size={}", - b.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) - ); - color_reset(f); - return Err(MismatchWasFound); - } else { - match a.byte_size() { - Some(size) => write!(f, " size={size}"), - None => write!(f, " size=?"), - }; - } - // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl - return Ok(KeepWriting); - } - (S::Array(ta, na), S::Array(tb, nb)) => { - if na != nb { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - - write!( - f, - "array<…, {}>", - match na { - Some(n) => n as &dyn Display, - None => (&"runtime-sized") as &dyn Display, - } - ); - - //a.writeln(indent, colored, f); - writeln!(f); - color_b(f); - write!(f, "{b_name}{SEP}"); - - write!( - f, - "array<…, {}>", - match nb { - Some(n) => n as &dyn Display, - None => (&"runtime-sized") as &dyn Display, - } - ); - - //b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } else { - write!(f, "array<"); - //ta.ty.write(&(indent.to_string() + tab), colored, f); - - Self::write( - &format!("{indent}{tab}"), - [(a_name, &ta.ty), (b_name, &tb.ty)], - colored, - f, - )?; - - if let Some(na) = na { - write!(f, ", {na}"); - } - write!(f, ">"); - - if ta.byte_stride != tb.byte_stride { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}> stride={}", ta.byte_stride); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}> stride={}", tb.byte_stride); - color_reset(f); - Err(MismatchWasFound) - } else { - // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl - write!(f, "> stride={}", ta.byte_stride); - return Ok(KeepWriting); - } - } - } - (S::Vector(na, ta), S::Vector(nb, tb)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - (S::Matrix(c, r, t), S::Matrix(c1, r1, t1)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - (S::PackedVector(p), S::PackedVector(p1)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - ( - // its written like this so that exhaustiveness checks lead us to this match statement if a type is added - S::Structure { .. } | S::Array { .. } | S::Vector { .. } | S::Matrix { .. } | S::PackedVector { .. }, - _, - ) => { - // TypeLayoutSemantics mismatch - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - } - } -} - -impl TypeLayout { - /// takes two pairs of `(debug_name, layout)` and compares them for equality. - /// - /// if the two layouts are not equal it uses the debug names in the returned - /// error to tell the two layouts apart. - pub(crate) fn check_eq(a: (&str, &TypeLayout), b: (&str, &TypeLayout)) -> Result<(), LayoutMismatch> { - match a.1 == b.1 { - true => Ok(()), - false => Err(LayoutMismatch { - layouts: [(a.0.into(), a.1.clone()), (b.0.into(), b.1.clone())], - colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(false), - }), - } - } -} diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs b/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs new file mode 100644 index 0000000..9f075d4 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs @@ -0,0 +1,298 @@ +use super::super::LayoutCalculator; +use super::*; + +// Size and align of host-shareable types // +// https://www.w3.org/TR/WGSL/#address-space-layout-constraints // + +#[derive(Debug, Clone, Copy)] +pub enum Repr { + /// Wgsl storage address space layout / OpenGL std430 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Storage, + /// Wgsl uniform address space layout / OpenGL std140 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Uniform, + /// Packed layout. Vertex buffer only. + Packed, +} + +impl SizedType { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn byte_size(&self, repr: Repr) -> u64 { + match self { + SizedType::Array(a) => a.byte_size(repr), + SizedType::Vector(v) => v.byte_size(), + SizedType::Matrix(m) => m.byte_size(Major::Row), + SizedType::Atomic(a) => a.byte_size(), + SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, + SizedType::Struct(s) => s.byte_size_and_align(repr).0, + } + } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + match self { + SizedType::Array(a) => a.align(repr), + SizedType::Vector(v) => v.align(), + SizedType::Matrix(m) => m.align(repr, Major::Row), + SizedType::Atomic(a) => a.align(), + SizedType::PackedVec(v) => v.align(), + SizedType::Struct(s) => s.byte_size_and_align(repr).1, + } + } + + /// This is expensive for structs. + pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + match self { + SizedType::Struct(s) => s.byte_size_and_align(repr), + non_struct => (non_struct.byte_size(repr), non_struct.byte_align(repr)), + } + } +} + + +impl SizedStruct { + pub fn field_offsets(&self, repr: Repr) -> FieldOffsets { + FieldOffsets { + fields: &self.fields, + field_index: 0, + calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + repr, + } + } + + /// Returns (byte_size, byte_align) + /// + /// ### Careful! + /// This is an expensive operation as it calculates byte size and align from scratch. + /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and + /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. + pub fn byte_size_and_align(&self, layout: Repr) -> (u64, U32PowerOf2) { + let mut field_offsets = self.field_offsets(layout); + (&mut field_offsets).count(); // &mut so it doesn't consume + (field_offsets.byte_size(), field_offsets.align()) + } +} + +/// An iterator over the field offsets of a `SizedStruct`. +/// +/// `FieldOffsets::byte_size` and `FieldOffsets::byte_align` can be used to query the struct's +/// `byte_size` and `byte_align`, but only takes into account the fields that have been iterated over. +pub struct FieldOffsets<'a> { + fields: &'a [SizedField], + field_index: usize, + calc: LayoutCalculator, + repr: Repr, +} + +impl Iterator for FieldOffsets<'_> { + type Item = u64; + + fn next(&mut self) -> Option { + self.field_index += 1; + self.fields.get(self.field_index - 1).map(|field| { + let (size, align) = match &field.ty { + SizedType::Struct(s) => { + let (size, align) = s.byte_size_and_align(self.repr); + match self.repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage | Repr::Packed => (size, align), + // https://www.w3.org/TR/WGSL/#address-space-layout-constraints + // The uniform address space requires that: + // - struct S align: roundUp(16, AlignOf(S)) + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + // -> We adjust size too. + Repr::Uniform => (round_up(16, size), round_up_align(U32PowerOf2::_16, align)), + } + } + non_struct => non_struct.byte_size_and_align(self.repr), + }; + + self.calc + .extend(size, align, field.custom_min_size, field.custom_min_align) + }) + } +} + +impl FieldOffsets<'_> { + /// Returns the byte size of the struct, but ONLY with the fields that have been iterated over so far. + pub const fn byte_size(&self) -> u64 { self.calc.byte_size() } + /// Returns the byte align of the struct, but ONLY with the fields that have been iterated over so far. + pub const fn align(&self) -> U32PowerOf2 { struct_align(self.calc.align(), self.repr) } +} + + +impl UnsizedStruct { + pub fn sized_field_offsets(&self, repr: Repr) -> FieldOffsets { + FieldOffsets { + fields: &self.sized_fields, + field_index: 0, + calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + repr, + } + } + + /// Returns (last field offset, byte align of unsized struct) + /// + /// `sized_field_offsets` must be from the same `UnsizedStruct`, otherwise the returned values + /// are not accurate. + pub fn last_field_offset_and_struct_align(&self, sized_field_offsets: FieldOffsets) -> (u64, U32PowerOf2) { + let mut offsets = sized_field_offsets; + // Iterating over any remaining field offsets to update the layout calculator. + (&mut offsets).count(); + + let array_align = self.last_unsized.array.byte_align(offsets.repr); + let custom_min_align = self.last_unsized.custom_min_align; + let (offset, align) = offsets.calc.extend_unsized(array_align, custom_min_align); + (offset, struct_align(align, offsets.repr)) + } + + /// This is expensive as it calculates the byte align from scratch. + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + let offsets = self.sized_field_offsets(repr); + self.last_field_offset_and_struct_align(offsets).1 + } +} + +const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage | Repr::Packed => align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + } +} + +impl Vector { + pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } + + pub const fn byte_size(&self) -> u64 { self.len.as_u64() * self.scalar.byte_size() } + + pub const fn align(&self) -> U32PowerOf2 { + let len = match self.len { + Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), + Len::X3 => 4, + }; + + // len * self.scalar.align() = power of 2 * power of 2 = power of 2 + U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()).unwrap() + } +} + +impl ScalarType { + pub const fn byte_size(&self) -> u64 { + match self { + ScalarType::F16 => 2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => 4, + ScalarType::F64 => 8, + } + } + + pub const fn align(&self) -> U32PowerOf2 { + match self { + ScalarType::F16 => U32PowerOf2::_2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, + ScalarType::F64 => U32PowerOf2::_8, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Major { + Row, + Column, +} + +impl Matrix { + pub const fn byte_size(&self, major: Major) -> u64 { + let (vec, array_len) = self.as_vector_array(major); + let array_stride = array_stride(vec.align(), vec.byte_size()); + array_size(array_stride, array_len) + } + + pub const fn align(&self, repr: Repr, major: Major) -> U32PowerOf2 { + let (vec, _) = self.as_vector_array(major); + array_align(vec.align(), repr) + } + + const fn as_vector_array(&self, major: Major) -> (Vector, NonZeroU32) { + let (vec_len, array_len): (Len, NonZeroU32) = match major { + Major::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), + Major::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), + }; + ( + Vector { + len: vec_len, + scalar: self.scalar.as_scalar_type(), + }, + array_len, + ) + } +} + +impl Atomic { + pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } + pub const fn align(&self) -> U32PowerOf2 { self.scalar.as_scalar_type().align() } +} + +impl SizedArray { + pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } + + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + + pub fn byte_stride(&self, repr: Repr) -> u64 { + let (element_size, element_align) = self.element.byte_size_and_align(repr); + array_stride(element_align, element_size) + } +} + +pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } + +pub const fn array_align(element_align: U32PowerOf2, layout: Repr) -> U32PowerOf2 { + match layout { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage | Repr::Packed => element_align, + Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), + } +} + +pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { + // Arrays of element type T must have an element stride that is a multiple of the + // RequiredAlignOf(T, C) for the address space C: + round_up(element_align.as_u64(), element_size) +} + +impl RuntimeSizedArray { + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.byte_align(repr), self.element.byte_size(repr)) } +} + +impl SizedField { + pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.byte_align(repr) } +} + +impl RuntimeSizedArrayField { + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.byte_align(repr) } +} + +pub const fn round_up(multiple_of: u64, n: u64) -> u64 { + match multiple_of { + 0 => match n { + 0 => 0, + _ => panic!("cannot round up n to a multiple of 0"), + }, + k @ 1.. => n.div_ceil(k) * k, + } +} + +pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32PowerOf2 { + let rounded_up = round_up(multiple_of.as_u64(), n.as_u64()); + // n <= multiple_of -> rounded_up = multiple_of + // n > multiple_of -> rounded_up = n, since both are powers of 2, n must already + // be a multiple of multiple_of + // In both cases rounded_up is a power of 2 + U32PowerOf2::try_from_u32(rounded_up as u32).unwrap() +} diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs b/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs new file mode 100644 index 0000000..c60e3fc --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs @@ -0,0 +1,165 @@ +use super::*; + +impl LayoutType { + pub fn struct_from_parts( + struct_name: impl Into, + fields: impl IntoIterator, + ) -> Result { + use StructFromPartsError::*; + + enum Field { + Sized(SizedField), + Unsized(RuntimeSizedArrayField), + } + + let mut fields = fields + .into_iter() + .map(|(options, ty)| { + Ok(match ty { + LayoutType::Sized(s) => Field::Sized(SizedField::new(options, s)), + LayoutType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( + options.name, + options.custom_min_align, + a.element, + )), + LayoutType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), + }) + }) + .peekable(); + + let mut sized_fields = Vec::new(); + let mut last_unsized = None; + while let Some(field) = fields.next() { + let field = field?; + match field { + Field::Sized(sized) => sized_fields.push(sized), + Field::Unsized(a) => { + last_unsized = Some(a); + if fields.peek().is_some() { + return Err(OnlyLastFieldMayBeUnsized); + } + } + } + } + + let field_count = sized_fields.len() + last_unsized.is_some() as usize; + if field_count == 0 { + return Err(MustHaveAtLeastOneField); + } + + if let Some(last_unsized) = last_unsized { + Ok(UnsizedStruct { + name: struct_name.into(), + sized_fields, + last_unsized, + } + .into()) + } else { + Ok(SizedStruct::from_parts(struct_name, sized_fields).into()) + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum StructFromPartsError { + #[error("Struct must have at least one field.")] + MustHaveAtLeastOneField, + #[error("Only the last field of a struct may be unsized.")] + OnlyLastFieldMayBeUnsized, + #[error("A field of the struct is an unsized struct, which isn't allowed.")] + MustNotHaveUnsizedStructField, +} + +impl SizedStruct { + /// Creates a new `SizedStruct` with one field. + /// + /// To add additional fields to it, use [`SizedStruct::extend`] or [`SizedStruct::extend_unsized`]. + pub fn new(name: impl Into, field_options: impl Into, ty: impl Into) -> Self { + Self { + name: name.into(), + fields: vec![SizedField::new(field_options, ty)], + } + } + + pub fn from_parts(name: impl Into, fields: Vec) -> Self { + Self { + name: name.into(), + fields, + } + } + + /// Adds a sized field to the struct. + pub fn extend(mut self, field_options: impl Into, ty: impl Into) -> Self { + self.fields.push(SizedField::new(field_options, ty)); + self + } + + /// Adds a runtime sized array field to the struct. This can only be the last + /// field of a struct, which is ensured by transitioning to an UnsizedStruct. + pub fn extend_unsized( + self, + name: impl Into, + custom_min_align: Option, + element_ty: impl Into, + ) -> UnsizedStruct { + UnsizedStruct { + name: self.name, + sized_fields: self.fields, + last_unsized: RuntimeSizedArrayField::new(name, custom_min_align, element_ty), + } + } + + /// Adds either a `SizedType` or a `RuntimeSizedArray` field to the struct. + /// + /// Returns a `HostshareableType`, because the `Self` may either stay + /// a `SizedStruct` or become an `UnsizedStruct` depending on the field's type. + pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutType { + let options = field_options.into(); + match field { + SizedOrArray::Sized(ty) => self.extend(options, ty).into(), + SizedOrArray::RuntimeSizedArray(a) => self + .extend_unsized(options.name, options.custom_min_align, a.element) + .into(), + } + } + + pub fn new_sized_or_array( + struct_name: impl Into, + field_options: impl Into, + field: SizedOrArray, + ) -> LayoutType { + let options = field_options.into(); + match field { + SizedOrArray::Sized(ty) => Self::new(struct_name, options, ty).into(), + SizedOrArray::RuntimeSizedArray(a) => UnsizedStruct { + name: struct_name.into(), + sized_fields: Vec::new(), + last_unsized: RuntimeSizedArrayField::new(options.name, options.custom_min_align, a.element), + } + .into(), + } + } + + pub fn fields(&self) -> &[SizedField] { &self.fields } +} + +impl> From<(T, SizedType)> for SizedField { + fn from(value: (T, SizedType)) -> Self { + Self { + name: value.0.into(), + ty: value.1, + custom_min_size: None, + custom_min_align: None, + } + } +} + +impl> From<(T, SizedType)> for RuntimeSizedArrayField { + fn from(value: (T, SizedType)) -> Self { + Self { + name: value.0.into(), + array: RuntimeSizedArray { element: value.1 }, + custom_min_align: None, + } + } +} diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs b/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs new file mode 100644 index 0000000..42f6a5e --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs @@ -0,0 +1,493 @@ +#![allow(missing_docs)] +use std::{num::NonZeroU32, rc::Rc}; + +use crate::{ + any::U32PowerOf2, + ir::{self, ir_type::BufferBlockDefinitionError, StructureFieldNamesMustBeUnique}, + GpuAligned, GpuSized, GpuStore, GpuType, NoBools, NoHandles, +}; + +// TODO(chronicl) +// - Consider moving this module into ir_type? +// - We borrow these types from `StoreType` currently. Maybe it would be better the other +// way around - `StoreType` should borrow from `HostShareableType`. +pub use crate::ir::{Len, Len2, LenEven, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; +use super::{constraint, FieldOptions}; + +mod align_size; +mod builder; + +pub use align_size::*; +pub use builder::*; + +// TODO(chronicl) rewrite +/// This reprsents a wgsl spec compliant host-shareable type with the addition +/// that f64 is a supported scalar type. +/// +/// https://www.w3.org/TR/WGSL/#host-shareable-types +#[derive(Debug, Clone)] +pub enum LayoutType { + Sized(SizedType), + UnsizedStruct(UnsizedStruct), + RuntimeSizedArray(RuntimeSizedArray), +} + +#[derive(Debug, Clone)] +pub enum SizedType { + Vector(Vector), + Matrix(Matrix), + Array(SizedArray), + Atomic(Atomic), + PackedVec(PackedVector), + Struct(SizedStruct), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Vector { + pub scalar: ScalarType, + pub len: Len, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Matrix { + pub scalar: ScalarTypeFp, + pub columns: Len2, + pub rows: Len2, +} + +#[derive(Debug, Clone)] +pub struct SizedArray { + pub element: Rc, + pub len: NonZeroU32, +} + +#[derive(Debug, Clone, Copy)] +pub struct Atomic { + pub scalar: ScalarTypeInteger, +} + +#[derive(Debug, Clone)] +pub struct RuntimeSizedArray { + pub element: SizedType, +} + +/// Same as `ir::ScalarType`, but without `ScalarType::Bool`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ScalarType { + F16, + F32, + U32, + I32, + F64, +} + +#[derive(Debug, Clone)] +pub struct SizedStruct { + pub name: CanonName, + // This is private to ensure a `SizedStruct` always has at least one field. + fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct UnsizedStruct { + pub name: CanonName, + pub sized_fields: Vec, + pub last_unsized: RuntimeSizedArrayField, +} + +#[derive(Debug, Clone)] +pub struct SizedField { + pub name: CanonName, + pub custom_min_size: Option, + pub custom_min_align: Option, + pub ty: SizedType, +} + +#[derive(Debug, Clone)] +pub struct RuntimeSizedArrayField { + pub name: CanonName, + pub custom_min_align: Option, + pub array: RuntimeSizedArray, +} + +impl SizedField { + pub fn new(options: impl Into, ty: impl Into) -> Self { + let options = options.into(); + Self { + name: options.name, + custom_min_size: options.custom_min_size, + custom_min_align: options.custom_min_align, + ty: ty.into(), + } + } +} + +impl RuntimeSizedArrayField { + pub fn new( + name: impl Into, + custom_min_align: Option, + element_ty: impl Into, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + array: RuntimeSizedArray { + element: element_ty.into(), + }, + } + } +} + +impl SizedArray { + pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { + Self { + element: Rc::new(element_ty.into()), + len, + } + } +} + +impl RuntimeSizedArray { + pub fn new(element_ty: impl Into) -> Self { + RuntimeSizedArray { + element: element_ty.into(), + } + } +} + +pub enum SizedOrArray { + Sized(SizedType), + RuntimeSizedArray(RuntimeSizedArray), +} + +#[derive(thiserror::Error, Debug)] +#[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] +pub struct IsUnsizedStruct; +impl TryFrom for SizedOrArray { + type Error = IsUnsizedStruct; + + fn try_from(value: LayoutType) -> Result { + match value { + LayoutType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), + LayoutType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), + LayoutType::UnsizedStruct(_) => Err(IsUnsizedStruct), + } + } +} + +// TODO(chronicl) documentation +pub trait BinaryRepr { + fn layout_type() -> LayoutType; +} +pub trait BinaryReprSized: BinaryRepr { + fn layout_type_sized() -> SizedType; +} + + +impl std::fmt::Display for ScalarType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ScalarType::F16 => "f16", + ScalarType::F32 => "f32", + ScalarType::F64 => "f64", + ScalarType::U32 => "u32", + ScalarType::I32 => "i32", + }) + } +} + +// Conversions to ScalarType, SizedType and HostshareableType // + +macro_rules! impl_into_sized_type { + ($($ty:ident -> $variant:path),*) => { + $( + impl $ty { + /// Const conversion to [`SizedType`] + pub const fn into_sized_type(self) -> SizedType { $variant(self) } + /// Const conversion to [`HostshareableType`] + pub const fn into_cpu_shareable(self) -> LayoutType { + LayoutType::Sized(self.into_sized_type()) + } + } + + impl From<$ty> for SizedType { + fn from(v: $ty) -> Self { v.into_sized_type() } + } + )* + }; +} +impl_into_sized_type!( + Vector -> SizedType::Vector, + Matrix -> SizedType::Matrix, + SizedArray -> SizedType::Array, + Atomic -> SizedType::Atomic, + SizedStruct -> SizedType::Struct, + PackedVector -> SizedType::PackedVec +); + +impl From for LayoutType +where + SizedType: From, +{ + fn from(value: T) -> Self { LayoutType::Sized(SizedType::from(value)) } +} + +impl From for LayoutType { + fn from(s: UnsizedStruct) -> Self { LayoutType::UnsizedStruct(s) } +} +impl From for LayoutType { + fn from(a: RuntimeSizedArray) -> Self { LayoutType::RuntimeSizedArray(a) } +} + +impl ScalarTypeInteger { + pub const fn as_scalar_type(self) -> ScalarType { + match self { + ScalarTypeInteger::I32 => ScalarType::I32, + ScalarTypeInteger::U32 => ScalarType::U32, + } + } +} +impl From for ScalarType { + fn from(int: ScalarTypeInteger) -> Self { int.as_scalar_type() } +} +impl ScalarTypeFp { + pub const fn as_scalar_type(self) -> ScalarType { + match self { + ScalarTypeFp::F16 => ScalarType::F16, + ScalarTypeFp::F32 => ScalarType::F32, + ScalarTypeFp::F64 => ScalarType::F64, + } + } +} +impl From for ScalarType { + fn from(int: ScalarTypeFp) -> Self { int.as_scalar_type() } +} + + +// Conversions to ir types // + +impl From for ir::StoreType { + fn from(host: LayoutType) -> Self { + match host { + LayoutType::Sized(s) => ir::StoreType::Sized(s.into()), + LayoutType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), + LayoutType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), + } + } +} + +impl From for ir::SizedType { + fn from(host: SizedType) -> Self { + match host { + SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), + SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), + SizedType::Array(a) => ir::SizedType::Array(Rc::new(Rc::unwrap_or_clone(a.element).into()), a.len), + SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), + // TODO(chronicl) check if this should be decompressed + SizedType::PackedVec(v) => v.decompressed_ty(), + SizedType::Struct(s) => ir::SizedType::Structure(s.into()), + } + } +} + +impl From for ir::ScalarType { + fn from(scalar_type: ScalarType) -> Self { + match scalar_type { + ScalarType::F16 => ir::ScalarType::F16, + ScalarType::F32 => ir::ScalarType::F32, + ScalarType::F64 => ir::ScalarType::F64, + ScalarType::U32 => ir::ScalarType::U32, + ScalarType::I32 => ir::ScalarType::I32, + } + } +} + +impl From for ir::ir_type::SizedStruct { + fn from(host: SizedStruct) -> Self { + let mut fields: Vec = host.fields.into_iter().map(Into::into).collect(); + // has at least one field + let last_field = fields.pop().unwrap(); + + // Note: This might throw an error in real usage if the fields aren't valid + // We're assuming they're valid in the conversion + match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { + Ok(s) => s, + Err(StructureFieldNamesMustBeUnique) => { + // TODO(chronicl) this isn't true + unreachable!("field names are unique for `LayoutType`") + } + } + } +} + +impl From for ir::ir_type::BufferBlock { + fn from(host: UnsizedStruct) -> Self { + let sized_fields: Vec = host.sized_fields.into_iter().map(Into::into).collect(); + + let last_unsized = host.last_unsized.into(); + + // TODO(chronicl) + // Note: This might throw an error in real usage if the struct isn't valid + // We're assuming it's valid in the conversion + match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { + Ok(b) => b, + Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => { + // TODO(chronicl) this isn't true + unreachable!("field names are unique for `UnsizedStruct`") + } + Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { + unreachable!("`UnsizedStruct` has at least one field.") + } + } + } +} + +impl From for ir::StoreType { + fn from(array: RuntimeSizedArray) -> Self { ir::StoreType::RuntimeSizedArray(array.element.into()) } +} + +impl From for ir::ir_type::SizedField { + fn from(f: SizedField) -> Self { ir::SizedField::new(f.name, f.custom_min_size, f.custom_min_align, f.ty.into()) } +} + +impl From for ir::ir_type::RuntimeSizedArrayField { + fn from(f: RuntimeSizedArrayField) -> Self { + ir::RuntimeSizedArrayField::new(f.name, f.custom_min_align, f.array.element.into()) + } +} + + +// Conversions from ir types // + +#[derive(thiserror::Error, Debug)] +#[error("Type contains bools, which isn't cpu shareable.")] +pub struct ContainsBools; + +impl TryFrom for ScalarType { + type Error = ContainsBools; + + fn try_from(value: ir::ScalarType) -> Result { + Ok(match value { + ir::ScalarType::F16 => ScalarType::F16, + ir::ScalarType::F32 => ScalarType::F32, + ir::ScalarType::F64 => ScalarType::F64, + ir::ScalarType::U32 => ScalarType::U32, + ir::ScalarType::I32 => ScalarType::I32, + ir::ScalarType::Bool => return Err(ContainsBools), + }) + } +} + +impl ir::ScalarType { + // TODO(chronicl) remove + pub fn as_host_shareable_unchecked(self) -> ScalarType { self.try_into().unwrap() } +} + +impl TryFrom for SizedType { + type Error = ContainsBools; + + fn try_from(value: ir::SizedType) -> Result { + Ok(match value { + ir::SizedType::Vector(len, scalar) => SizedType::Vector(Vector { + scalar: scalar.try_into()?, + len, + }), + ir::SizedType::Matrix(columns, rows, scalar) => SizedType::Matrix(Matrix { scalar, columns, rows }), + ir::SizedType::Array(element, len) => SizedType::Array(SizedArray { + element: Rc::new((*element).clone().try_into()?), + len, + }), + ir::SizedType::Atomic(scalar_type) => SizedType::Atomic(Atomic { scalar: scalar_type }), + ir::SizedType::Structure(structure) => SizedType::Struct(structure.try_into()?), + }) + } +} + +impl TryFrom for SizedStruct { + type Error = ContainsBools; + + fn try_from(structure: ir::ir_type::SizedStruct) -> Result { + let mut fields = Vec::new(); + + for field in structure.sized_fields() { + fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + Ok(SizedStruct { + name: structure.name().clone(), + fields, + }) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum CpuShareableConversionError { + #[error("Type contains bools, which isn't cpu shareable.")] + ContainsBool, + #[error("Type is a handle, which isn't cpu shareable.")] + IsHandle, +} + +impl From for CpuShareableConversionError { + fn from(_: ContainsBools) -> Self { Self::ContainsBool } +} + +impl TryFrom for LayoutType { + type Error = CpuShareableConversionError; + + fn try_from(value: ir::StoreType) -> Result { + Ok(match value { + ir::StoreType::Sized(sized_type) => LayoutType::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => LayoutType::RuntimeSizedArray(RuntimeSizedArray { + element: element.try_into()?, + }), + ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, + ir::StoreType::Handle(_) => return Err(CpuShareableConversionError::IsHandle), + }) + } +} + +impl TryFrom for LayoutType { + type Error = ContainsBools; + + fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { + let mut sized_fields = Vec::new(); + + for field in buffer_block.sized_fields() { + sized_fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + let last_unsized = if let Some(last_field) = buffer_block.last_unsized_field() { + RuntimeSizedArrayField { + name: last_field.name.clone(), + custom_min_align: last_field.custom_min_align, + array: RuntimeSizedArray { + element: last_field.element_ty.clone().try_into()?, + }, + } + } else { + return Ok(SizedStruct { + name: buffer_block.name().clone(), + fields: sized_fields, + } + .into()); + }; + + Ok(UnsizedStruct { + name: buffer_block.name().clone(), + sized_fields, + last_unsized, + } + .into()) + } +} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs new file mode 100644 index 0000000..bc43f74 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -0,0 +1,452 @@ +use super::*; + +/// Error of two layouts mismatching. Implements Display for a visualization of the mismatch. +#[derive(Clone)] +pub struct LayoutMismatch { + /// 2 (name, layout) pairs + layouts: [(String, TypeLayout); 2], + colored_error: bool, +} + + +impl std::fmt::Debug for LayoutMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) // use Display + } +} + +impl LayoutMismatch { + fn pad_width(name_a: &str, name_b: &str) -> usize { name_a.chars().count().max(name_b.chars().count()) + SEP.len() } +} + +impl Display for LayoutMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let colored = self.colored_error; + let [(a_name, a), (b_name, b)] = &self.layouts; + write!(f, "{:width$}", ' ', width = Self::pad_width(a_name, b_name))?; + let layouts = [(a_name.as_str(), a), (b_name.as_str(), b)]; + match LayoutMismatch::write("", layouts, colored, f) { + Err(MismatchWasFound) => Ok(()), + Ok(KeepWriting) => { + writeln!( + f, + "" + )?; + writeln!(f, "the full type layouts in question are:")?; + for (name, layout) in layouts { + writeln!(f, "`{}`:", name)?; + writeln!(f, "{}", layout)?; + } + Ok(()) + } + } + } +} + +/// layout mismatch diff name separator +/// used in the display impl of LayoutMismatch to show the actual place where the layouts mismatch +/// ``` +/// layout mismatch: +/// cpu{SEP} f32 +/// gpu{SEP} i32 +/// ``` +const SEP: &str = ": "; + +/// whether the mismatching part of the TypeLayouts in a LayoutMismatch was already expressed via writes. +/// indicates that the `write` function should stop writing. +pub(crate) struct MismatchWasFound; +pub(crate) struct KeepWriting; + +impl LayoutMismatch { + //TODO(low prio) try to figure out a cleaner way of writing these. + + /// this function uses the `Err(MismatchWasFound)` to halt traversing the typelayout. + /// It does not constitute an error of this function, it is just so the ? operator can be used to propagate the abort. + #[allow(clippy::needless_return)] + pub(crate) fn write( + indent: &str, + layouts: [(&str, &TypeLayout); 2], + colored: bool, + f: &mut W, + ) -> Result { + let tab = " "; + let [(a_name, a), (b_name, b)] = layouts; + + if a == b { + a.write(indent, colored, f); + return Ok(KeepWriting); + } + + let use_256_color_mode = false; + let hex_color = |f_: &mut W, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let color_reset = |f_: &mut W| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + let color_a_hex = "#DF5853"; + let color_b_hex = "#9A639C"; + let color_a = |f| hex_color(f, color_a_hex); + let color_b = |f| hex_color(f, color_b_hex); + + let pad_width = Self::pad_width(a_name, b_name); + + use TypeLayoutSemantics as S; + match (&a.kind, &b.kind) { + (S::Structure(sa), S::Structure(sb)) => { + let max_fields = sa.all_fields().len().max(sb.all_fields().len()); + { + write!(f, "struct "); + hex_color(f, color_a_hex); + write!(f, "{}", sa.name); + color_reset(f); + write!(f, " / "); + hex_color(f, color_b_hex); + write!(f, "{}", sb.name); + color_reset(f); + writeln!(f, " {{"); + } + + let mut sa_fields = sa.all_fields().iter(); + let mut sb_fields = sb.all_fields().iter(); + + loop { + //TODO(low prio) get a hold of the code duplication here + match (sa_fields.next(), sb_fields.next()) { + (Some(a_field), Some(b_field)) => { + let offsets_match = a_field.rel_byte_offset == b_field.rel_byte_offset; + let types_match = a_field.field.ty == b_field.field.ty; + if !offsets_match && types_match { + // only write this mismatch if the types are also the same, otherwise display the detailed type mismatch further below + let a_ty_string = a_field.field.ty.first_line_of_display_with_ellipsis(); + let b_ty_string = b_field.field.ty.first_line_of_display_with_ellipsis(); + color_a(f); + writeln!( + f, + "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", + a_field.rel_byte_offset, + a_field.field.name, + a_field.field.byte_align().as_u32() + ); + color_b(f); + writeln!( + f, + "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", + b_field.rel_byte_offset, + b_field.field.name, + b_field.field.byte_align().as_u32() + ); + color_reset(f); + writeln!( + f, + "field offset is different on {a_name} ({}) and {b_name} ({}).", + a_field.rel_byte_offset, b_field.rel_byte_offset + ); + return Err(MismatchWasFound); + } + let offset = a_field.rel_byte_offset; + let a_field = &a_field.field; + let b_field = &b_field.field; + let field = &a_field; + + if offsets_match { + write!(f, "{:width$}{indent}{offset:3} ", ' ', width = pad_width); + } else { + write!(f, "{:width$}{indent} ? ", ' ', width = pad_width); + } + if a_field.name != b_field.name { + writeln!(f); + color_a(f); + writeln!(f, "{a_name}{SEP}{indent} {}: …", a_field.name); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent} {}: …", b_field.name); + color_reset(f); + writeln!( + f, + "identifier mismatch, either\nfield '{}' is missing on {a_name}, or\nfield '{}' is missing on {b_name}.", + b_field.name, a_field.name + ); + return Err(MismatchWasFound); + } + write!(f, "{}: ", field.name); + if a_field.ty != b_field.ty { + Self::write( + &format!("{indent}{tab}"), + [(a_name, &a_field.ty), (b_name, &b_field.ty)], + colored, + f, + )?; + return Err(MismatchWasFound); + } + write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); + if a_field.byte_size() != b_field.byte_size() { + writeln!(f); + color_a(f); + writeln!( + f, + "{a_name}{SEP}{indent} size={}", + a_field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + color_b(f); + writeln!( + f, + "{b_name}{SEP}{indent} size={}", + b_field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + color_reset(f); + return Err(MismatchWasFound); + } + + write!( + f, + " size={}", + field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + writeln!(f, ","); + } + (Some(a_field), None) => { + let offset = a_field.rel_byte_offset; + let a_field = &a_field.field; + let field = &a_field; + color_a(f); + write!(f, "{a_name}{SEP}{indent}{offset:3} "); + write!(f, "{}: ", field.name); + write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); + write!( + f, + " size={}", + field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + writeln!(f, ","); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent}", a_field.name); + color_reset(f); + return Err(MismatchWasFound); + } + (None, Some(b_field)) => { + let offset = b_field.rel_byte_offset; + let b_field = &b_field.field; + color_a(f); + writeln!(f, "{a_name}{SEP}{indent}", b_field.name); + let field = &b_field; + color_b(f); + write!(f, "{b_name}{SEP}{indent}{offset:3} "); + write!(f, "{}: ", field.name); + write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); + write!( + f, + " size={}", + field + .byte_size() + .as_ref() + .map(|x| x as &dyn Display) + .unwrap_or(&"?" as _) + ); + writeln!(f, ","); + color_reset(f); + return Err(MismatchWasFound); + } + (None, None) => break, + } + } + + write!(f, "{:width$}{indent}}}", ' ', width = pad_width); + let align_matches = a.byte_align() == b.byte_align(); + let size_matches = a.byte_size() == b.byte_size(); + if !align_matches && size_matches { + writeln!(f); + color_a(f); + writeln!(f, "{a_name}{SEP}{indent}align={}", a.byte_align().as_u32()); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent}align={}", b.byte_align().as_u32()); + color_reset(f); + return Err(MismatchWasFound); + } else { + match align_matches { + true => write!(f, " align={}", a.byte_align().as_u32()), + false => write!(f, " align=?"), + }; + } + if !size_matches { + writeln!(f); + color_a(f); + writeln!( + f, + "{a_name}{SEP}{indent}size={}", + a.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) + ); + color_b(f); + writeln!( + f, + "{b_name}{SEP}{indent}size={}", + b.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) + ); + color_reset(f); + return Err(MismatchWasFound); + } else { + match a.byte_size() { + Some(size) => write!(f, " size={size}"), + None => write!(f, " size=?"), + }; + } + // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl + return Ok(KeepWriting); + } + (S::Array(ta, na), S::Array(tb, nb)) => { + if na != nb { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + + write!( + f, + "array<…, {}>", + match na { + Some(n) => n as &dyn Display, + None => (&"runtime-sized") as &dyn Display, + } + ); + + //a.writeln(indent, colored, f); + writeln!(f); + color_b(f); + write!(f, "{b_name}{SEP}"); + + write!( + f, + "array<…, {}>", + match nb { + Some(n) => n as &dyn Display, + None => (&"runtime-sized") as &dyn Display, + } + ); + + //b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } else { + write!(f, "array<"); + //ta.ty.write(&(indent.to_string() + tab), colored, f); + + Self::write( + &format!("{indent}{tab}"), + [(a_name, &ta.ty), (b_name, &tb.ty)], + colored, + f, + )?; + + if let Some(na) = na { + write!(f, ", {na}"); + } + write!(f, ">"); + + if ta.byte_stride != tb.byte_stride { + writeln!(f); + color_a(f); + writeln!(f, "{a_name}{SEP}{indent}> stride={}", ta.byte_stride); + color_b(f); + writeln!(f, "{b_name}{SEP}{indent}> stride={}", tb.byte_stride); + color_reset(f); + Err(MismatchWasFound) + } else { + // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl + write!(f, "> stride={}", ta.byte_stride); + return Ok(KeepWriting); + } + } + } + (S::Vector(_), S::Vector(_)) => { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + (S::Matrix(_), S::Matrix(_)) => { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + (S::PackedVector(p), S::PackedVector(p1)) => { + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + ( + // its written like this so that exhaustiveness checks lead us to this match statement if a type is added + S::Structure { .. } | S::Array { .. } | S::Vector { .. } | S::Matrix { .. } | S::PackedVector { .. }, + _, + ) => { + // TypeLayoutSemantics mismatch + writeln!(f); + color_a(f); + write!(f, "{a_name}{SEP}"); + a.writeln(indent, colored, f); + color_b(f); + write!(f, "{b_name}{SEP}"); + b.writeln(indent, colored, f); + color_reset(f); + Err(MismatchWasFound) + } + } + } +} + +/// takes two pairs of `(debug_name, layout)` and compares them for equality. +/// +/// if the two layouts are not equal it uses the debug names in the returned +/// error to tell the two layouts apart. +pub(crate) fn check_eq( + a: (&str, &TypeLayout), + b: (&str, &TypeLayout), +) -> Result<(), LayoutMismatch> +where + TypeLayout: PartialEq>, +{ + match a.1 == b.1 { + true => Ok(()), + false => Err(LayoutMismatch { + layouts: [ + (a.0.into(), a.1.to_owned().into_plain()), + (b.0.into(), b.1.to_owned().into_plain()), + ], + colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(false), + }), + } +} diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs new file mode 100644 index 0000000..bc4a432 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -0,0 +1,616 @@ +#![allow(missing_docs)] // TODO(chronicl) remove +//! Everything related to type layouts. + + + +use std::{ + fmt::{Debug, Display, Write}, + hash::Hash, + marker::PhantomData, + rc::Rc, +}; + +use crate::{ + any::U32PowerOf2, + call_info, + common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, + ir::{ + self, + ir_type::{round_up, CanonName, ScalarTypeFp}, + recording::Context, + Len, SizedType, Type, + }, +}; +use cpu_shareable::{LayoutType, Matrix, Vector}; +use thiserror::Error; + +pub mod cpu_shareable; +mod eq; +mod storage_uniform; +mod vertex; + +pub use vertex::*; +pub use storage_uniform::*; +pub(crate) use eq::*; + +/// The type contained in the bytes of a `TypeLayout`. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypeLayoutSemantics { + // TODO(chronicl) consider replacing this scalar type with the host shareable version. + /// `vec` + Vector(Vector), + /// special compressed vectors for vertex attribute types + /// + /// see the [`crate::packed`] module + PackedVector(ir::PackedVector), + /// `mat`, first `Len2` is cols, 2nd `Len2` is rows + Matrix(Matrix), + /// `Array` and `Array>` + Array(Rc, Option), // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. + /// structures which may be empty and may have an unsized last field + Structure(Rc), +} + +/// The memory layout of a type. +/// +/// This models only the layout, not other characteristics of the types. +/// For example an `Atomic>` is treated like a regular `vec` layout wise. +/// +/// ### Constraint generic +/// +/// `TypeLayout` has a generic `T: TypeConstraint`, which guarantees that the layout +/// follows certain layout rules. The following layout rules exist and are captured by +/// the [`Repr`] enum: +/// ``` +/// pub enum Repr { +/// Storage, /// wgsl storage address space layout / OpenGL std430 +/// Uniform, /// wgsl uniform address space layout / OpenGL std140 +/// Packed, /// packed layout, vertex buffer only +/// } +/// ``` +/// +/// More information on the exact details of these layout rules is available here +/// +/// https://www.w3.org/TR/WGSL/#address-space-layout-constraints +/// +/// The following types implementing `TypeConstraint` exist and can be found in [`constraint`]: +/// +/// ``` +/// struct Plain; /// May not follow any layout rules. +/// struct Storage; /// Follows `Repr::Storage` layout rules. +/// struct Uniform; /// Follows `Repr::Uniform` layout rules. +/// struct Vertex; /// Follows either `Repr::Packed` or both +/// /// `Repr::Storage` and `Repr::Uniform`. +/// ``` +/// `Vertex` also guarantees that it is either the layout of a [`VertexAttribute`] or the layout +/// of a struct with fields that are all `VertexAttribute`s, which is why `Repr::Storage` +/// and `Repr::Uniform` are equivalent for it. +/// +/// `Storage` and `Uniform` are always based on a [`CpuShareableType`], which can be accessed +/// using [`TypeLayout::cpu_shareable`]. They also guarantee, that all nested `TypeLayout`s +/// (for example struct fields) are also based on a `CpuShareableType`, which can only be accessed +/// through `TypeLayout::get_cpu_shareable`. +/// +/// TODO(chronicl) make sure these are caught: +/// There is some target language specific constraints that `TypeLayout` does not capture: +/// - `TypeLayout` may be unsized, which is not allowed for wgsl. +/// - `custom_min_align` and `custom_min_size` are not supported for glsl. +/// +/// ### Layout comparison +/// +/// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question +/// "do these two types have the same layout" so that uploading a type to the gpu +/// will result in no memory errors. +/// +/// A layout comparison looks like this: +/// ``` +/// assert!(f32::cpu_layout() == vec::gpu_layout().into_unconstraint()); +/// // or, more explicitly +/// assert_eq!( +/// ::cpu_layout(), +/// as GpuLayout>::gpu_layout(), +/// ); +/// ``` +/// +#[derive(Clone)] +pub struct TypeLayout { + /// size in bytes (Some), or unsized (None) + pub byte_size: Option, + /// the byte alignment + /// + /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) + pub byte_align: U32PowerOf2, + /// the type contained in the bytes of this type layout + pub kind: TypeLayoutSemantics, + + /// Is some for `constraint::Storage` and `constraint::Uniform`. Can be converted to a `StoreType`. + pub(crate) cpu_shareable: Option, + _phantom: PhantomData, +} + +// PartialEq, Eq, Hash for TypeLayout +impl PartialEq> for TypeLayout { + fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } +} +impl Eq for TypeLayout {} +impl Hash for TypeLayout { + fn hash(&self, state: &mut H) { + self.byte_size.hash(state); + self.kind.hash(state); + } +} + +use constraint::TypeConstraint; +/// Module for all restrictions on `TypeLayout`. +pub mod constraint { + use super::*; + + /// Type restriction of a `TypeLayout`. This allows type layouts + /// to have guarantees based on their type restriction. For example a + /// `TypeLayout can be used in vertex buffers. For more + /// details see the documentation of [`TypeLayout`]. + pub trait TypeConstraint: Clone + PartialEq + Eq {} + + macro_rules! type_restriction { + ($($constraint:ident),*) => { + $( + /// A type restriction for `TypeLayout`. + /// See [`TypeRestriction`] documentation for TODO(chronicl) + #[derive(Clone, PartialEq, Eq, Hash)] + pub struct $constraint; + impl TypeConstraint for $constraint {} + )* + }; + } + + type_restriction!(Plain, Storage, Uniform, Vertex); + + macro_rules! impl_from_into { + ($from:ident -> $($into:ident),*) => { + $( + impl From> for TypeLayout<$into> { + fn from(layout: TypeLayout<$from>) -> Self { type_layout_internal::cast_unchecked(layout) } + } + )* + }; + } + + impl_from_into!(Storage -> Plain); + impl_from_into!(Uniform -> Plain, Storage); + impl_from_into!(Vertex -> Plain); +} + +impl TypeLayout { + pub(crate) fn new( + byte_size: Option, + byte_align: U32PowerOf2, + kind: TypeLayoutSemantics, + hostshareable: Option, + ) -> Self { + TypeLayout { + byte_size, + byte_align, + kind, + cpu_shareable: hostshareable, + _phantom: PhantomData, + } + } +} + +/// This module offers helper methods that do not adhere to the restriction of `TypeLayout. +/// The caller must uphold these restrictions themselves. +/// The main purpose of this module is to avoid repetition. +pub(in super::super::rust_types) mod type_layout_internal { + use super::*; + + pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { + TypeLayout { + byte_size: layout.byte_size, + byte_align: layout.byte_align, + kind: layout.kind, + cpu_shareable: layout.cpu_shareable, + _phantom: PhantomData, + } + } +} + +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a gpu struct. +/// +/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s +/// are ignored and the field is directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the `packedness` of the field. +#[derive(Debug, Clone)] +pub struct LayoutCalculator { + next_offset_min: u64, + align: U32PowerOf2, + packed: bool, +} + +impl LayoutCalculator { + /// Creates a new `LayoutCalculator`, which calculates the size, align and + /// the field offsets of a gpu struct. + pub const fn new(packed: bool) -> Self { + Self { + next_offset_min: 0, + align: U32PowerOf2::_1, + packed, + } + } + + /// Extends the layout by a field given it's size and align. + /// + /// Returns the field's offset. + pub const fn extend( + &mut self, + field_size: u64, + field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + ) -> u64 { + let size = FieldLayout::calculate_byte_size(field_size, custom_min_size); + let align = FieldLayout::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.next_offset_min = offset + size; + self.align = self.align.max(align); + + offset + } + + /// Extends the layout by a field given it's size and align. If the field + /// is unsized, pass `None` as it's size. + /// + /// Returns (byte size, byte align, last field offset). + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_maybe_unsized( + mut self, + field_size: Option, + field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + ) -> (Option, U32PowerOf2, u64) { + if let Some(size) = field_size { + let offset = self.extend(size, field_align, custom_min_size, custom_min_align); + (Some(self.byte_size()), self.align(), offset) + } else { + let (offset, align) = self.extend_unsized(field_align, custom_min_align); + (None, align, offset) + } + } + + + /// Extends the layout by an unsized field given it's align. + /// + /// Returns (last field offset, align) + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_unsized( + mut self, + field_align: U32PowerOf2, + custom_min_align: Option, + ) -> (u64, U32PowerOf2) { + let align = FieldLayout::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.align = self.align.max(align); + + (offset, self.align) + } + + /// Returns the byte size of the struct. + // wgsl spec: + // roundUp(AlignOf(S), justPastLastMember) + // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) + // + // self.next_offset_min is justPastLastMember already. + pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } + + /// Returns the align of the struct. + pub const fn align(&self) -> U32PowerOf2 { self.align } + + /// field_align should already respect field_custom_min_align. + /// field_custom_min_align is used to overwrite packing if self is packed. + const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { + match (self.packed, field_custom_min_align) { + (true, None) => self.next_offset_min, + (true, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (false, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } +} + +/// a sized or unsized struct type with 0 or more fields +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StructLayout { + pub name: IgnoreInEqOrdHash, + pub fields: Vec, +} + +impl StructLayout { + /// this exists, because if in the future a refactor happens that separates + /// fields into sized and unsized fields, the intention of this function is + /// clear + fn all_fields(&self) -> &[FieldLayoutWithOffset] { &self.fields } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldLayoutWithOffset { + pub field: FieldLayout, + pub rel_byte_offset: u64, // this being relative is used in TypeLayout::byte_size +} + +impl std::ops::Deref for FieldLayoutWithOffset { + type Target = FieldLayout; + fn deref(&self) -> &Self::Target { &self.field } +} + +/// Describes the layout of the elements of an array. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ElementLayout { + /// Stride of the elements + pub byte_stride: u64, + /// The elment layout must be constraint::Plain, because it's shared by all constraints. + // `ElementLayout` could possibly be made generic too, but it would complicate a lot. + pub ty: TypeLayout, +} + +/// the layout rules used when calculating the byte offsets and alignment of a type +#[derive(Debug, Clone, Copy)] +pub enum TypeLayoutRules { + /// wgsl type layout rules, see https://www.w3.org/TR/WGSL/#memory-layouts + Wgsl, + // reprC, + // Std140, + // Std430, + // Scalar, +} + +#[allow(missing_docs)] +#[derive(Error, Debug, Clone)] +pub enum TypeLayoutError { + #[error("An array cannot contain elements of an unsized type {elements}")] + ArrayOfUnsizedElements { elements: TypeLayout }, + #[error("the type `{0}` has no defined {1:?} layout in shaders")] + LayoutUndefined(Type, TypeLayoutRules), + #[error("in `{parent_name}` at field `{field_name}`: {error}")] + AtField { + parent_name: CanonName, + field_name: CanonName, + error: Rc, + }, + #[error("in element of array: {0}")] + InArrayElement(Rc), +} + +impl TypeLayout { + /// Byte size of the represented type. + pub fn byte_size(&self) -> Option { self.byte_size } + + /// Align of the represented type. + pub fn byte_align(&self) -> U32PowerOf2 { self.byte_align } + + /// Although all TypeLayout always implement Into, this method + /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. + pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } + + /// Get the `CpuShareableType` this layout is based on. + /// + /// This may be `None` for type layouts + /// that aren't `TypeLayout` or `TypeLayout`. Those two also offer direct + /// access via [`TypeLayout::cpu_shareable`]. + pub fn get_cpu_shareable(&self) -> Option<&LayoutType> { self.cpu_shareable.as_ref() } + + /// a short name for this `TypeLayout`, useful for printing inline + pub fn short_name(&self) -> String { + match &self.kind { + TypeLayoutSemantics::Vector { .. } | + TypeLayoutSemantics::PackedVector { .. } | + TypeLayoutSemantics::Matrix { .. } => format!("{}", self), + TypeLayoutSemantics::Array(element_layout, n) => match n { + Some(n) => format!("array<{}, {n}>", element_layout.ty.short_name()), + None => format!("array<{}, runtime-sized>", element_layout.ty.short_name()), + }, + TypeLayoutSemantics::Structure(s) => s.name.to_string(), + } + } + + pub(crate) fn first_line_of_display_with_ellipsis(&self) -> String { + let string = format!("{}", self); + string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) + } + + pub(crate) fn writeln(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { + self.write(indent, colored, f)?; + writeln!(f) + } + + //TODO(low prio) try to figure out a cleaner way of writing these. + pub(crate) fn write(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { + let tab = " "; + let use_256_color_mode = false; + let color = |f_: &mut W, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset = |f_: &mut W| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + use TypeLayoutSemantics as Sem; + + match &self.kind { + Sem::Vector(Vector { len: l, scalar: t }) => match l { + Len::X1 => write!(f, "{t}")?, + l => write!(f, "{t}x{}", u64::from(*l))?, + }, + Sem::PackedVector(c) => write!(f, "{}", c)?, + Sem::Matrix(m) => write!(f, "{}", ir::SizedType::Matrix(m.columns, m.rows, m.scalar))?, + Sem::Array(t, n) => { + let stride = t.byte_stride; + write!(f, "array<")?; + t.ty.write(&(indent.to_string() + tab), colored, f)?; + if let Some(n) = n { + write!(f, ", {n}")?; + } + write!(f, "> stride={stride}")?; + } + Sem::Structure(s) => { + writeln!(f, "struct {} {{", s.name)?; + { + let indent = indent.to_string() + tab; + for field in &s.fields { + let offset = field.rel_byte_offset; + let field = &field.field; + write!(f, "{indent}{offset:3} {}: ", field.name)?; + field.ty.write(&(indent.to_string() + tab), colored, f)?; + if let Some(size) = field.ty.byte_size { + let size = size.max(field.custom_min_size.unwrap_or(0)); + write!(f, " size={size}")?; + } else { + write!(f, " size=?")?; + } + writeln!(f, ",")?; + } + } + write!(f, "{indent}}}")?; + write!(f, " align={}", self.byte_align.as_u64())?; + if let Some(size) = self.byte_size { + write!(f, " size={size}")?; + } else { + write!(f, " size=?")?; + } + } + }; + Ok(()) + } +} + +impl TypeLayout { + pub(crate) fn from_rust_sized(kind: TypeLayoutSemantics) -> Self { + Self::new( + Some(size_of::() as u64), + // align is always a power of 2: + // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.align + U32PowerOf2::try_from(align_of::() as u32).unwrap(), + kind, + None, + ) + } + + // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings + pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + let t: cpu_shareable::LayoutType = store_type.try_into()?; + Ok(TypeLayout::new_storage_layout_for(t).into()) + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldLayout { + pub name: CanonName, + // whether size/align is custom doesn't matter for the layout equality. + pub custom_min_size: IgnoreInEqOrdHash>, + pub custom_min_align: IgnoreInEqOrdHash>, + /// The fields layout must be constraint::Plain, + /// because that's the most it can be while supporting all possible constraints. + pub ty: TypeLayout, +} + +impl FieldLayout { + pub fn new( + name: CanonName, + custom_min_size: Option, + custom_min_align: Option, + ty: TypeLayout, + ) -> Self { + Self { + name, + custom_min_size: custom_min_size.into(), + custom_min_align: custom_min_align.into(), + ty, + } + } + + fn byte_size(&self) -> Option { + self.ty + .byte_size() + .map(|byte_size| Self::calculate_byte_size(byte_size, self.custom_min_size.0)) + } + + /// The alignment of the field with `custom_min_align` taken into account. + fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.byte_align(), self.custom_min_align.0) } + + const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1) as u32 as u64) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align + } + } +} + +/// Options for the field of a struct. +/// +/// If you only want to customize the field's name, you can convert most string types +/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, +/// meaning you can just pass the string type directly. +#[derive(Debug, Clone)] +pub struct FieldOptions { + /// Name of the field + pub name: CanonName, + /// Custom minimum align of the field. + pub custom_min_align: Option, + /// Custom mininum size of the field. + pub custom_min_size: Option, +} + +impl FieldOptions { + /// Creates new `FieldOptions`. + /// + /// If you only want to customize the field's name, you can convert most string types + /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, + /// meaning you can just pass the string type directly. + pub fn new( + name: impl Into, + custom_min_align: Option, + custom_min_size: Option, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + custom_min_size, + } + } +} + +impl> From for FieldOptions { + fn from(name: T) -> Self { Self::new(name, None, None) } +} + +impl Display for TypeLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); + self.write("", colored, f) + } +} + +impl Debug for TypeLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } +} diff --git a/shame/src/frontend/rust_types/type_layout/storage_uniform.rs b/shame/src/frontend/rust_types/type_layout/storage_uniform.rs new file mode 100644 index 0000000..d788a71 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/storage_uniform.rs @@ -0,0 +1,475 @@ +use crate::{ + __private::SmallVec, + cpu_shareable::{self as cs, Major, Repr, SizedField, UnsizedStruct}, + ir::{ir_type::max_u64_po2_dividing, StoreType}, +}; +use super::*; +use TypeLayoutSemantics as TLS; + +impl TypeLayout { + /// Takes a `SizedType`, because wgsl only supports sized uniform buffers. + /// Use `new_layout_for_unchecked`to obtain the the uniform layout of an unsized host-shareable. /// Using the unsized layout with wgsl as your target language will cause an error. + pub fn new_uniform_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Uniform)) + } + + /// Get the `CpuShareableType` this layout is based on. + pub fn cpu_shareable(&self) -> &LayoutType { + self.cpu_shareable + .as_ref() + .expect("constraint::Uniform always contains a cpu-shareable") + } +} + +impl TypeLayout { + pub fn new_storage_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) + } + + /// Get the `CpuShareableType` this layout is based on. + pub fn cpu_shareable(&self) -> &LayoutType { + self.cpu_shareable + .as_ref() + .expect("constraint::Storage always contains a cpu-shareable") + } +} + +impl TypeLayout { + pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { + match ty.into() { + LayoutType::Sized(ty) => Self::from_sized_type(ty, repr), + LayoutType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), + LayoutType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), + } + } + + fn from_sized_type(ty: cs::SizedType, repr: Repr) -> Self { + let (size, align, tls) = match &ty { + cs::SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), + cs::SizedType::Atomic(a) => ( + a.byte_size(), + a.align(), + TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), + ), + cs::SizedType::Matrix(m) => (m.byte_size(Major::Row), m.align(repr, Major::Row), TLS::Matrix(*m)), + cs::SizedType::Array(a) => ( + a.byte_size(repr), + a.align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: a.byte_stride(repr), + ty: Self::from_sized_type((*a.element).clone(), repr), + }), + Some(a.len.get()), + ), + ), + cs::SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), + cs::SizedType::Struct(s) => { + let mut field_offsets = s.field_offsets(repr); + let fields = (&mut field_offsets) + .zip(s.fields()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect(); + + ( + field_offsets.byte_size(), + field_offsets.align(), + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + ) + } + }; + + TypeLayout::new(Some(size), align, tls, Some(ty.into())) + } + + fn from_unsized_struct(s: UnsizedStruct, repr: Repr) -> Self { + let mut field_offsets = s.sized_field_offsets(repr); + let mut fields = (&mut field_offsets) + .zip(s.sized_fields.iter()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect::>(); + + let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + fields.push(FieldLayoutWithOffset { + rel_byte_offset: field_offset, + field: FieldLayout { + name: s.last_unsized.name.clone(), + custom_min_size: None.into(), + custom_min_align: s.last_unsized.custom_min_align.into(), + ty: Self::from_runtime_sized_array(s.last_unsized.array.clone(), repr), + }, + }); + + TypeLayout::new( + None, + align, + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + Some(s.into()), + ) + } + + + fn from_runtime_sized_array(ty: cs::RuntimeSizedArray, repr: Repr) -> Self { + Self::new( + None, + ty.byte_align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: ty.byte_stride(repr), + ty: Self::from_sized_type(ty.element.clone(), repr), + }), + None, + ), + Some(ty.into()), + ) + } +} + +fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + FieldLayoutWithOffset { + rel_byte_offset: offset, + field: FieldLayout { + name: field.name.clone(), + custom_min_size: field.custom_min_size.into(), + custom_min_align: field.custom_min_align.into(), + ty: TypeLayout::from_sized_type(field.ty.clone(), repr), + }, + } +} + +impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { + type Error = UniformLayoutError; + + fn try_from(s_layout: &'a TypeLayout) -> Result { + let cpu_shareable = s_layout.cpu_shareable().clone(); + + // Checking whether it's sized + let sized = match cpu_shareable { + LayoutType::Sized(sized) => sized, + _ => { + return Err(UniformLayoutError::MustBeSized("wgsl", cpu_shareable.into())); + } + }; + + let u_layout = TypeLayout::new_uniform_layout_for(sized); + + // Checking fields + fn check_layout( + s_layout: &TypeLayout, + u_layout: &TypeLayout, + is_top_level: bool, + ) -> Result<(), Mismatch> { + // kinds are the same, because the type layouts are based on the same cpu shareable + match (&s_layout.kind, &u_layout.kind) { + (TLS::Structure(s), TLS::Structure(u)) => { + for (i, (s_field, u_field)) in s.fields.iter().zip(u.fields.iter()).enumerate() { + // Checking field offset + if s_field.rel_byte_offset != u_field.rel_byte_offset { + let (sized_fields, last_unsized) = match s_layout.get_cpu_shareable().unwrap() { + LayoutType::Sized(cs::SizedType::Struct(s)) => (s.fields().to_vec(), None), + LayoutType::UnsizedStruct(s) => (s.sized_fields.clone(), Some(s.last_unsized.clone())), + _ => unreachable!("is struct, because tls is struct"), + }; + + return Err(Mismatch::StructureFieldOffset(StructureFieldOffsetError { + struct_layout: (**s).clone(), + sized_fields, + last_unsized, + field_name: s_field.field.name.clone(), + field_index: i, + actual_offset: s_field.rel_byte_offset, + expected_alignment: u_field.field.byte_align(), + is_top_level, + })); + } + + check_layout(&s_field.field.ty, &u_field.field.ty, false)?; + } + } + (TLS::Array(s_ele, _), TLS::Array(u_ele, _)) => { + // As long as the strides are the same, the size must be the same and + // the element align must divide the stride (uniform requirement), because + // u_layout is a valid uniform layout. + if s_ele.byte_stride != u_ele.byte_stride { + let element_ty = match u_ele.ty.get_cpu_shareable().unwrap() { + LayoutType::Sized(s) => s.clone(), + _ => { + unreachable!("elements of an array are always sized for TypeLayout") + } + }; + return Err(Mismatch::ArrayStride(ArrayStrideError { + expected: u_ele.byte_stride, + actual: s_ele.byte_stride, + element_ty, + })); + } + + check_layout(&s_ele.ty, &u_ele.ty, false)?; + } + _ => {} + } + + Ok(()) + } + + + match check_layout(s_layout, &u_layout, true) { + Ok(()) => Ok(u_layout), + Err(e) => { + let ctx = LayoutErrorContext { + s_layout: s_layout.clone(), + u_layout, + // TODO(chronicl) default shouldn't be true? + use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(true), + }; + + use UniformLayoutError as U; + let e = match e { + Mismatch::ArrayStride(e) => U::ArrayStride(WithContext::new(ctx, e)), + Mismatch::StructureFieldOffset(e) => U::StructureFieldOffset(WithContext::new(ctx, e)), + }; + Err(e) + } + } + } +} + +type DepthIndex = SmallVec; + +#[derive(thiserror::Error, Debug, Clone)] +pub enum UniformLayoutError { + #[error("{0}")] + ArrayStride(WithContext), + #[error("{0}")] + StructureFieldOffset(WithContext), + // TODO(chronicl) consider using CpuShareableType and implementing Display for it instead + // of converting to StoreType + #[error( + "The size of `{1}` on the gpu is not known at compile time. `{0}` \ + requires that the size of uniform buffers on the gpu is known at compile time." + )] + MustBeSized(&'static str, StoreType), +} + +#[derive(Debug, Clone)] +pub enum Mismatch { + ArrayStride(ArrayStrideError), + StructureFieldOffset(StructureFieldOffsetError), +} + + +#[derive(Debug, Clone)] +pub struct LayoutErrorContext { + s_layout: TypeLayout, + u_layout: TypeLayout, + use_color: bool, +} + +#[derive(Debug, Clone)] +pub struct WithContext { + ctx: LayoutErrorContext, + inner: T, +} + +impl std::ops::Deref for WithContext { + type Target = T; + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl WithContext { + fn new(ctx: LayoutErrorContext, inner: T) -> Self { Self { ctx, inner } } +} + + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct ArrayStrideError { + expected: u64, + actual: u64, + element_ty: cs::SizedType, +} + +impl std::error::Error for WithContext {} +impl Display for WithContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let top_level: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + writeln!( + f, + "array elements within type `{}` do not satisfy uniform layout requirements.", + top_level + )?; + writeln!( + f, + "The array with `{}` elements requires stride {}, but has stride {}.", + Into::::into(self.element_ty.clone()), + self.expected, + self.actual + )?; + writeln!(f, "The full layout of `{}` is:", top_level)?; + self.ctx.s_layout.write("", self.ctx.use_color, f)?; + writeln!(f)?; + writeln!( + f, + "\nfor more information on the layout rules, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + )?; + Ok(()) + } +} + + +#[derive(Debug, Clone)] +pub struct StructureFieldOffsetError { + pub struct_layout: StructLayout, + pub sized_fields: Vec, + pub last_unsized: Option, + pub field_name: CanonName, + pub field_index: usize, + pub actual_offset: u64, + pub expected_alignment: U32PowerOf2, + pub is_top_level: bool, +} + +impl std::error::Error for WithContext {} +impl Display for WithContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO(chronicl) remove conversion here if CpuShareableType implements Display + let top_level_type: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + writeln!( + f, + "The type `{top_level_type}` cannot be used as a uniform buffer binding." + )?; + + match self.is_top_level { + true => write!(f, "Struct `{}`", &*self.struct_layout.name)?, + false => write!(f, "It contains a struct `{}`, which", &*self.struct_layout.name)?, + } + writeln!(f, " does not satisfy the uniform memory layout requirements.",)?; + writeln!(f)?; + // TODO(chronicl) structure_def_location + // if let Some(call_info) = structure_def_location { + // writeln!(f, "Definition at {call_info}")?; + // } + + write_struct_layout( + &self.struct_layout, + &self.sized_fields, + self.last_unsized.as_ref(), + self.ctx.use_color, + Some(self.field_index), + f, + )?; + + let actual_align = max_u64_po2_dividing(self.actual_offset); + let expected_alignment = self.expected_alignment.as_u32(); + writeln!(f)?; + set_color(f, Some("#508EE3"), false)?; + writeln!( + f, + "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", + self.field_name, expected_alignment, self.actual_offset + )?; + set_color(f, None, false)?; + writeln!(f)?; + + writeln!(f, "Potential solutions include:"); + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", + expected_alignment, self.field_name + ); + writeln!(f, "- use a storage binding instead of a uniform binding"); + writeln!( + f, + "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", + self.field_name, expected_alignment + ); + writeln!(f)?; + + writeln!( + f, + "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.\nMore info about the uniform address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints" + )?; + Ok(()) + } +} + +/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. +fn write_struct_layout( + struct_layout: &StructLayout, + sized_fields: &[cs::SizedField], + last_unsized: Option<&cs::RuntimeSizedArrayField>, + colored: bool, + highlight_field: Option, + f: &mut F, +) -> std::fmt::Result +where + F: Write, +{ + let use_256_color_mode = false; + let color = |f_: &mut F, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset = |f_: &mut F| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + let struct_name = &*struct_layout.name; + + let indent = " "; + let field_decl_line = |field: &cs::SizedField| { + let sized: ir::SizedType = field.ty.clone().into(); + format!("{indent}{}: {},", field.name, sized) + }; + let header = format!("struct {} {{", struct_name); + let table_start_column = 1 + sized_fields + .iter() + .map(field_decl_line) + .map(|s| s.len()) + .max() + .unwrap_or(0) + .max(header.chars().count()); + f.write_str(&header)?; + for i in header.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "offset align size")?; + for (i, (field, layout)) in sized_fields.iter().zip(struct_layout.fields.iter()).enumerate() { + if Some(i) == highlight_field { + color(f, "#508EE3")?; + } + let (align, size) = (layout.ty.byte_align(), layout.ty.byte_size().expect("is sized field")); + let decl_line = field_decl_line(field); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "{:6} {:5} {:4}", layout.rel_byte_offset, align.as_u32(), size)?; + if Some(i) == highlight_field { + reset(f)?; + } + } + if let Some(last_field) = last_unsized { + let layout = struct_layout.fields.last().expect("structs have at least one field"); + + let store_type: ir::StoreType = last_field.array.clone().into(); + let decl_line = format!("{indent}{}: {},", last_field.name, store_type); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.byte_align().as_u32())?; + } + writeln!(f, "}}")?; + Ok(()) +} diff --git a/shame/src/frontend/rust_types/type_layout/vertex.rs b/shame/src/frontend/rust_types/type_layout/vertex.rs new file mode 100644 index 0000000..3fe905e --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/vertex.rs @@ -0,0 +1,100 @@ +use crate::{any::VertexAttribFormat, cpu_shareable::Repr}; +use super::{*}; + +impl TypeLayout { + pub fn from_vertex_attribute(attribute: VertexAttribFormat) -> TypeLayout { + let (size, align, kind) = match attribute { + VertexAttribFormat::Fine(len, scalar) => { + let sized = cpu_shareable::Vector::new(scalar, len); + (sized.byte_size(), sized.align(), TypeLayoutSemantics::Vector(sized)) + } + VertexAttribFormat::Coarse(packed) => ( + u8::from(packed.byte_size()) as u64, + U32PowerOf2::try_from(packed.align() as u32).unwrap(), + TypeLayoutSemantics::PackedVector(packed), + ), + }; + + type_layout_internal::cast_unchecked(TypeLayout::new(Some(size), align, kind, None)) + } + + /// Creates a new builder for a `TypeLayout`. Takes the first + /// attribute immediately, because at least one attribut needs to exist. + /// + /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` + /// are equivalent for vertex layouts. + pub fn vertex_builder( + struct_name: impl Into, + field_options: impl Into, + attribute: VertexAttribFormat, + rules: Repr, + ) -> VertexLayoutBuilder { + VertexLayoutBuilder::new(struct_name, field_options, attribute, rules) + } +} + +pub struct VertexLayoutBuilder { + name: CanonName, + attributes: Vec, + rules: Repr, +} + +impl VertexLayoutBuilder { + /// Creates a new builder for a `TypeLayout`. Takes the first + /// attribute immediately, because at least one attribut needs to exist. + /// + /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` + /// are equivalent for vertex layouts. + pub fn new( + struct_name: impl Into, + field_options: impl Into, + attribute: VertexAttribFormat, + rules: Repr, + ) -> Self { + let this = VertexLayoutBuilder { + name: struct_name.into(), + attributes: Vec::new(), + rules, + }; + this.extend(field_options, attribute) + } + + pub fn extend(mut self, field_options: impl Into, attribute: VertexAttribFormat) -> Self { + let layout = TypeLayout::from_vertex_attribute(attribute); + let options = field_options.into(); + self.attributes.push(FieldLayout::new( + options.name, + options.custom_min_size, + options.custom_min_align, + layout.into(), + )); + self + } + + pub fn finish(self) -> TypeLayout { + let mut calc = LayoutCalculator::new(matches!(self.rules, Repr::Packed)); + let fields = self + .attributes + .into_iter() + .map(|field| { + let rel_byte_offset = calc.extend( + field.byte_size().unwrap(), // attributes are always sized + field.byte_align(), + *field.custom_min_size, + *field.custom_min_align, + ); + FieldLayoutWithOffset { field, rel_byte_offset } + }) + .collect::>(); + + type_layout_internal::cast_unchecked(TypeLayout::new( + Some(calc.byte_size()), + calc.align(), + TypeLayoutSemantics::Structure(Rc::new(StructLayout { + name: self.name.into(), + fields, + })), + None, + )) + } +} diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 0157f57..7d01d4e 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -3,9 +3,14 @@ use super::{ layout_traits::{FromAnys, GetAllFields, GpuLayout}, mem::{self, AddressSpace}, reference::{AccessMode, AccessModeReadable}, + type_layout::{self, type_layout_internal}, AsAny, GpuType, ToGpuType, }; -use crate::frontend::any::shared_io::{BindPath, BindingType}; +use crate::{ + frontend::any::shared_io::{BindPath, BindingType}, + cpu_shareable::{LayoutType}, + TypeLayout, +}; use crate::{ call_info, common::proc_macro_utils::push_wrong_amount_of_args_error, @@ -60,7 +65,7 @@ pub struct BindingArgs { //[old-doc] //[old-doc] corresponds to WGSL "Storable type" https://www.w3.org/TR/WGSL/#storable-types /// (no documentation yet) -pub trait GpuStore: GpuAligned + GetAllFields + FromAnys { +pub trait GpuStore: GetAllFields + FromAnys { /// the type whose public immutable interface is exposed by [`shame::Ref`]: /// /// ` as std::ops::Deref>::Target` @@ -168,7 +173,7 @@ pub trait GpuSized: GpuAligned { )] /// ## known byte-alignment on the gpu /// types that have a byte-alignment on the graphics device that is known at rust compile-time -pub trait GpuAligned: GpuLayout { +pub trait GpuAligned { #[doc(hidden)] // runtime api fn aligned_ty() -> AlignedType where @@ -186,7 +191,7 @@ pub trait GpuAligned: GpuLayout { /// /// boolean types do not have a defined size on gpus. /// You may want to use unsigned integers for transferring boolean data instead. -pub trait NoBools: GpuLayout {} +pub trait NoBools {} /// (no documentation yet) #[diagnostic::on_unimplemented( @@ -197,7 +202,7 @@ pub trait NoBools: GpuLayout {} // error message isn't misleading for user provided types `T`. Those types will show // the base trait diagnostic, instead of "`T` contains `XYZ`" which it doesn't. /// types that don't contain atomics at any nesting level -pub trait NoAtomics: GpuLayout {} +pub trait NoAtomics {} // implementor note: // NoXYZ traits must require GpuLayout or some other base trait, so that the @@ -207,13 +212,15 @@ pub trait NoAtomics: GpuLayout {} message = "`{Self}` may be or contain a handle type such as `Texture`, `Sampler`, `StorageTexture`." )] /// Implemented by types that aren't/contain no textures, storage textures, their array variants or samplers -pub trait NoHandles: GpuLayout {} +pub trait NoHandles {} /// this trait is only implemented by: /// /// * `sm::vec`s of non-boolean type (e.g. `sm::f32x4`) /// * `sm::packed::PackedVec`s (e.g. `sm::packed::unorm8x4`) -pub trait VertexAttribute: GpuLayout + FromAnys { +// Is at most 16 bytes according to https://www.w3.org/TR/WGSL/#input-output-locations +// and thus GpuSized. +pub trait VertexAttribute: GpuLayout + FromAnys + GpuSized { #[doc(hidden)] // runtime api fn vertex_attrib_format() -> VertexAttribFormat; } diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 977c2e4..b25587e 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,7 +7,10 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, - type_layout::TypeLayoutRules, + type_layout::{ + cpu_shareable::{self, BinaryReprSized}, + TypeLayoutRules, + }, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; @@ -17,6 +20,7 @@ use crate::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, small_vec::SmallVec, }, + cpu_shareable::BinaryRepr, frontend::encoding::{ buffer::{BufferAddressSpace, BufferInner, BufferRefInner}, rasterizer::Gradient, @@ -68,12 +72,12 @@ pub type scalar = vec; /// let my_vec3 = sm::vec!(1.0, 2.0, 3.0); /// let my_vec4 = sm::vec!(my_vec3, 0.0); // component concatenation, like usual in shaders /// let my_vec4 = my_vec3.extend(0.0); // or like this -/// +/// /// let my_normal = sm::vec!(1.0, 1.0, 0.0).normalize(); /// let rgb = my_normal.remap(-1.0..=1.0, 0.0..=1.0); // remap linear ranges (instead of " * 0.5 + 0.5") /// /// let alpha = 0.4.to_gpu(); // convert from rust to `shame` types (also works for arrays and structs) -/// let smooth: f32x1 = alpha.smoothstep(0.4..0.8); +/// let smooth: f32x1 = alpha.smootcstep(0.4..0.8); /// /// // clamp as generalized min, max, clamp via half open ranges /// let upper = alpha.clamp(..=0.8); @@ -572,8 +576,27 @@ impl GpuStore for vec { fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } } -impl GpuLayout for vec { - fn gpu_layout() -> TypeLayout { TypeLayout::from_sized_ty(TypeLayoutRules::Wgsl, &::sized_ty()) } + +impl BinaryReprSized for vec +where + vec: NoBools, +{ + fn layout_type_sized() -> cpu_shareable::SizedType { + cpu_shareable::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() + } +} +impl BinaryRepr for vec +where + vec: NoBools, +{ + fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +} + +impl GpuLayout for vec +where + vec: NoBools, +{ + fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> @@ -1095,7 +1118,9 @@ impl VertexAttribute for vec where Self: NoBools, { - fn vertex_attrib_format() -> VertexAttribFormat { VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE) } + fn vertex_attrib_format() -> VertexAttribFormat { + VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.as_host_shareable_unchecked()) + } } impl FromAnys for vec { diff --git a/shame/src/ir/ir_type/align_size.rs b/shame/src/ir/ir_type/align_size.rs index 3916e09..1ed703a 100644 --- a/shame/src/ir/ir_type/align_size.rs +++ b/shame/src/ir/ir_type/align_size.rs @@ -8,11 +8,11 @@ use thiserror::Error; use super::{CanonName, Len2, ScalarType, ScalarTypeFp, SizedType, StoreType}; use super::{Len, Std}; -pub fn round_up(multiple_of: u64, n: u64) -> u64 { +pub const fn round_up(multiple_of: u64, n: u64) -> u64 { match multiple_of { 0 => match n { 0 => 0, - n => panic!("cannot round up {n} to a multiple of 0"), + n => panic!("cannot round up n to a multiple of 0"), }, k @ 1.. => n.div_ceil(k) * k, } diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index 8d7b80f..5af9ed9 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -507,7 +507,7 @@ impl Display for ArrayStrideAlignmentError { "The array with `{}` elements requires that every element is {expected_align}-byte aligned, but the array has a stride of {actual_stride} bytes, which means subsequent elements are not {expected_align}-byte aligned.", self.element_ty ); - if let Ok(layout) = TypeLayout::from_store_ty(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { + if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); layout.write("", self.ctx.use_color, f)?; writeln!(f); @@ -542,7 +542,7 @@ impl Display for ArrayStrideError { "The array with `{}` elements requires stride {}, but has stride {}.", self.element_ty, self.expected, self.actual ); - if let Ok(layout) = TypeLayout::from_store_ty(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { + if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); layout.write("", self.ctx.use_color, f)?; writeln!(f); @@ -577,7 +577,7 @@ impl Display for ArrayAlignmentError { "The array with `{}` elements requires alignment {}, but has alignment {}.", self.element_ty, self.expected, self.actual ); - if let Ok(layout) = TypeLayout::from_store_ty(TypeLayoutRules::Wgsl, &self.ctx.top_level_type) { + if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); layout.write("", self.ctx.use_color, f)?; writeln!(f); diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 9933b87..64e7282 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -1,6 +1,7 @@ use std::{fmt::Display, num::NonZeroU32}; use crate::{ + any::U32PowerOf2, common::floating_point::{f16, f32_eq_where_nans_are_equal, f64_eq_where_nans_are_equal}, ir::Comp4, }; @@ -73,23 +74,11 @@ impl PartialEq for Len { } impl From for u64 { - fn from(value: Len2) -> Self { - match value { - Len2::X2 => 2, - Len2::X3 => 3, - Len2::X4 => 4, - } - } + fn from(value: Len2) -> Self { value.as_u64() } } impl From for NonZeroU32 { - fn from(value: Len2) -> Self { - match value { - Len2::X2 => NonZeroU32::new(2).unwrap(), - Len2::X3 => NonZeroU32::new(3).unwrap(), - Len2::X4 => NonZeroU32::new(4).unwrap(), - } - } + fn from(value: Len2) -> Self { value.as_non_zero_u32() } } impl PartialEq for Len2 { @@ -102,6 +91,54 @@ impl Len { use Comp4::*; [X, Y, Z, W].into_iter().take(self.into()) } + + /// as u8 + pub const fn as_u8(self) -> u8 { + match self { + Len::X1 => 1, + Len::X2 => 2, + Len::X3 => 3, + Len::X4 => 4, + } + } + /// as u32 + pub const fn as_u32(self) -> u32 { self.as_u8() as u32 } + /// as u64 + pub const fn as_u64(self) -> u64 { self.as_u8() as u64 } + /// as `NonZeroU32` + pub const fn as_non_zero_u32(self) -> NonZeroU32 { + match self { + Len::X1 => NonZeroU32::new(1).unwrap(), + Len::X2 => NonZeroU32::new(2).unwrap(), + Len::X3 => NonZeroU32::new(3).unwrap(), + Len::X4 => NonZeroU32::new(4).unwrap(), + } + } +} + +impl Len2 { + /// as u8 + pub const fn as_u8(self) -> u8 { + match self { + Len2::X2 => 2, + Len2::X3 => 3, + Len2::X4 => 4, + } + } + /// as u32 + pub const fn as_u32(self) -> u32 { self.as_u8() as u32 } + /// as u64 + pub const fn as_u64(self) -> u64 { self.as_u8() as u64 } + /// as `NonZeroU32` + pub const fn as_non_zero_u32(self) -> NonZeroU32 { self.as_len().as_non_zero_u32() } + /// as [`Len`] + pub const fn as_len(self) -> Len { + match self { + Len2::X2 => Len::X2, + Len2::X3 => Len::X3, + Len2::X4 => Len::X4, + } + } } /// (no documentation yet) @@ -458,6 +495,12 @@ impl From for u8 { } } +impl PackedVectorByteSize { + pub fn as_u32(self) -> u32 { u8::from(self) as u32 } + + pub fn as_u64(self) -> u64 { u8::from(self) as u64 } +} + impl Display for PackedVector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let stype = match self.scalar_type { @@ -485,12 +528,13 @@ impl PackedVector { } } - pub fn align(&self) -> u64 { - match self.byte_size() { + pub fn align(&self) -> U32PowerOf2 { + let align = match self.byte_size() { PackedVectorByteSize::_2 => SizedType::Vector(Len::X1, ScalarType::F16).align(), PackedVectorByteSize::_4 => SizedType::Vector(Len::X1, ScalarType::U32).align(), PackedVectorByteSize::_8 => SizedType::Vector(Len::X2, ScalarType::U32).align(), - } + }; + U32PowerOf2::try_from(align as u32).expect("the above all have power of 2 align") } } diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 4e9e1f0..57e21fd 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -16,6 +16,7 @@ use crate::{ po2::U32PowerOf2, pool::{Key, PoolRef}, }, + cpu_shareable, frontend::{ any::{ render_io::{Attrib, ColorTarget, Location, VertexBufferLayout}, @@ -43,7 +44,8 @@ use crate::{ StructureFieldNamesMustBeUnique, TextureFormatWrapper, Type, }, results::DepthStencilState, - BindingIter, DepthLhs, StencilMasking, Test, + type_layout::TypeLayoutSemantics, + BindingIter, DepthLhs, StencilMasking, Test, TypeLayout, }; @@ -71,7 +73,7 @@ macro_rules! stringify_checked { #[derive(Error, Debug, Clone)] pub enum PipelineError { - #[error("Missing pipeline specialization. Use either the `{}` or `{}` method to start a compute- or render-pipeline encoding.", + #[error("Missing pipeline specialization. Use either the `{}` or `{}` method to start a compute- or render-pipeline encoding.", stringify_checked!(expr: EncodingGuard::new_compute_pipeline::<3>).replace("3", "_"), stringify_checked!(expr: EncodingGuard::new_render_pipeline), )] @@ -352,7 +354,14 @@ impl WipPushConstantsField { let byte_size = sized_struct.byte_size(); // TODO(release) the `.expect()` calls here can be removed by building a `std::alloc::Layout`-like builder for struct layouts. - let (_, _, layout) = StructLayout::from_ir_struct(TypeLayoutRules::Wgsl, &sized_struct); + let sized_struct: cpu_shareable::SizedStruct = sized_struct + .try_into() + .expect("push constants are NoBools and NoHandles"); + let layout = TypeLayout::new_storage_layout_for(sized_struct); + let layout = match &layout.kind { + TypeLayoutSemantics::Structure(layout) => &**layout, + _ => unreachable!("expected struct layout for type layout of struct"), + }; let mut ranges = ByteRangesPerStage::default(); @@ -408,7 +417,7 @@ impl WipPushConstantsField { // here we have to allocate unique name strings for each field, // so we don't fail the name uniqueness check, even though we don't need those names. - SizedStruct::new_nonempty("PushConstants".into(), + SizedStruct::new_nonempty("PushConstants".into(), fields.iter().map(&mut to_sized_field).collect(), to_sized_field(last) ).map_err(|err| match err { diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 9d316df..b54475b 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -184,7 +184,7 @@ pub use frontend::rust_types::vec_range_traits::VecRangeBoundsInclusive; pub mod aliases { use crate::frontend::rust_types::aliases; - #[rustfmt::skip] + #[rustfmt::skip] pub use aliases::rust_simd::{ f16x1, f32x1, f64x1, u32x1, i32x1, boolx1, f16x2, f32x2, f64x2, u32x2, i32x2, boolx2, @@ -192,16 +192,16 @@ pub mod aliases { f16x4, f32x4, f64x4, u32x4, i32x4, boolx4, }; - #[rustfmt::skip] + #[rustfmt::skip] pub use aliases::rust_simd::{ f16x2x2, f32x2x2, f64x2x2, f16x2x3, f32x2x3, f64x2x3, f16x2x4, f32x2x4, f64x2x4, - + f16x3x2, f32x3x2, f64x3x2, f16x3x3, f32x3x3, f64x3x3, f16x3x4, f32x3x4, f64x3x4, - + f16x4x2, f32x4x2, f64x4x2, f16x4x3, f32x4x3, f64x4x3, f16x4x4, f32x4x4, f64x4x4, @@ -316,8 +316,10 @@ pub use shame_derive::CpuLayout; pub use shame_derive::GpuLayout; pub use frontend::rust_types::layout_traits::GpuLayout; pub use frontend::rust_types::layout_traits::CpuLayout; +pub use frontend::rust_types::type_layout; pub use frontend::rust_types::type_layout::TypeLayout; pub use frontend::rust_types::type_layout::TypeLayoutError; +pub use frontend::rust_types::type_layout::cpu_shareable; pub use frontend::rust_types::layout_traits::ArrayElementsUnsizedError; // derived traits @@ -331,6 +333,7 @@ pub use frontend::rust_types::struct_::SizedFields; pub use frontend::rust_types::type_traits::NoBools; pub use frontend::rust_types::type_traits::NoAtomics; pub use frontend::rust_types::type_traits::NoHandles; +pub use frontend::rust_types::type_traits::VertexAttribute; pub use frontend::rust_types::layout_traits::VertexLayout; diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index b137a23..855d8a9 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -248,9 +248,9 @@ fn unsized_struct_vec3_align_layout_eq() { } assert_ne!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); assert!(OnGpu::gpu_layout().byte_size() == Some(16)); - assert!(OnGpu::gpu_layout().align() == 16); + assert!(OnGpu::gpu_layout().byte_align().as_u32() == 16); assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().align() == 4); + assert!(OnCpu::cpu_layout().byte_align().as_u32() == 4); } #[test] diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index c9fe351..45a1446 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -8,6 +8,7 @@ use syn::LitInt; use syn::{DataStruct, DeriveInput, FieldsNamed}; use crate::util; +use crate::util::Repr; macro_rules! bail { ($span: expr, $display: expr) => {return Err(syn::Error::new($span, $display,))}; @@ -81,12 +82,17 @@ pub fn impl_for_struct( .into_iter(); let none_if_no_cpu_equivalent_type = cpu_attr.is_none().then_some(quote! { None }).into_iter(); - // #[gpu_repr(packed)] - let gpu_repr_packed = util::find_gpu_repr_packed(&input.attrs)?; - if let (Some(span), WhichDerive::CpuLayout) = (&gpu_repr_packed, &which_derive) { + // #[gpu_repr(packed | storage | uniform)] + let gpu_repr = util::determine_gpu_repr(&input.attrs)?; + if let (Some((span, _)), WhichDerive::CpuLayout) = (&gpu_repr, &which_derive) { bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") } - let is_gpu_repr_packed = gpu_repr_packed.is_some(); + let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); + let gpu_repr_shame = match gpu_repr { + Repr::Packed => quote!( #re::Repr::Packed ), + Repr::Storage => quote!( #re::Repr::Storage ), + Repr::Uniform => quote!( #re::Repr::Uniform ), + }; // #[repr(...)] let repr_c_attr = util::try_parse_repr(&input.attrs)?; @@ -194,35 +200,69 @@ pub fn impl_for_struct( match which_derive { WhichDerive::GpuLayout => { - let impl_gpu_layout = quote! { - impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> + let layout_type_fn = quote! { + let result = #re::LayoutType::struct_from_parts( + std::stringify!(#derive_struct_ident), + [ + #(( + #re::FieldOptions::new( + std::stringify!(#field_ident), + #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), + #field_size.into(), + ), + <#field_type as #re::BinaryRepr>::layout_type() + ),)* + ] + ); + + match result { + Ok(layout_type) => layout_type, + Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), + Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), + // GpuType is not implemented for derived structs directly, so they can't be used + // as the field of another struct, instead shame::Struct has to be used, which + // only accepts sized structs. + Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), + } + }; + + let impl_binary_repr = quote! { + impl<#generics_decl> #re::BinaryRepr for #derive_struct_ident<#(#idents_of_generics),*> where - #(#first_fields_type: #re::GpuSized,)* - #last_field_type: #re::GpuAligned, + // These NoBools and NoHandle bounds are only for better diagnostics, BinaryRepr already implies them + #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::BinaryReprSized,)* + #last_field_type: #re::NoBools + #re::NoHandles + #re::BinaryRepr, #where_clause_predicates { + fn layout_type() -> #re::LayoutType { + #layout_type_fn + } + } - fn gpu_layout() -> #re::TypeLayout { - let result = #re::TypeLayout::struct_from_parts( - #re::TypeLayoutRules::Wgsl, - #is_gpu_repr_packed, - std::stringify!(#derive_struct_ident).into(), - [ - #( - #re::FieldLayout { - name: std::stringify!(#field_ident).into(), - ty: <#field_type as #re::GpuLayout>::gpu_layout(), - custom_min_align: #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), - custom_min_size: #field_size.into(), - }, - )* - ].into_iter() - ); - match result { - Ok(layout) => layout, - Err(e @ #re::StructLayoutError::UnsizedFieldMustBeLast { .. }) => unreachable!("all fields except last require bound `GpuSized`. {e}"), + impl<#generics_decl> #re::BinaryReprSized for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::BinaryReprSized,)* + #where_clause_predicates + { + fn layout_type_sized() -> #re::SizedType { + match { #layout_type_fn } { + #re::LayoutType::Sized(s) => s, + _ => unreachable!("ensured by BinaryReprSized field trait bounds above") } } + } + }; + + let impl_gpu_layout = quote! { + impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#first_fields_type: #re::BinaryReprSized,)* + #last_field_type: #re::BinaryRepr, + #where_clause_predicates + { + fn gpu_repr() -> #re::Repr { + #gpu_repr_shame + } fn cpu_type_name_and_layout() -> Option, #re::TypeLayout), #re::ArrayElementsUnsizedError>> { use #re::CpuLayout as _; @@ -243,8 +283,7 @@ pub fn impl_for_struct( where #(#triv #field_type: #re::VertexAttribute,)* #where_clause_predicates - { - } + { } }; @@ -322,10 +361,11 @@ pub fn impl_for_struct( } }; - if gpu_repr_packed.is_some() { + if matches!(gpu_repr, Repr::Packed) { // this is basically only for vertex buffers, so // we only implement `GpuLayout` and `VertexLayout`, as well as their implied traits Ok(quote! { + #impl_binary_repr #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits @@ -334,12 +374,13 @@ pub fn impl_for_struct( } else { // non gpu_repr(packed) let struct_ref_doc = format!( - r#"This struct was generated by `#[derive(shame::GpuLayout)]` - as a version of `{derive_struct_ident}` which holds references to its fields. It is used as + r#"This struct was generated by `#[derive(shame::GpuLayout)]` + as a version of `{derive_struct_ident}` which holds references to its fields. It is used as the `std::ops::Deref` target of `shame::Ref<{derive_struct_ident}>`"# ); Ok(quote! { + #impl_binary_repr #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index afa6930..0e085ed 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -27,16 +27,32 @@ pub fn find_literal_list_attr( Ok(None) } -pub fn find_gpu_repr_packed(attribs: &[syn::Attribute]) -> Result> { +pub enum Repr { + Packed, + Storage, + Uniform, +} + +pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result> { + let mut repr = Repr::Storage; for a in attribs { if a.path().is_ident("gpu_repr") { a.parse_nested_meta(|meta| { if meta.path.is_ident("packed") { + repr = Repr::Packed; + return Ok(()); + } else if meta.path.is_ident("storage") { + repr = Repr::Storage; + return Ok(()); + } else if meta.path.is_ident("uniform") { + repr = Repr::Uniform; return Ok(()); } + Err(meta.error("unrecognized `gpu_repr`. Did you mean `gpu_repr(packed)`?")) })?; - return Ok(Some(a.span())); + + return Ok(Some((a.span(), repr))); } } Ok(None) From 4b7e4a4564db08738036115bf6a8e491c86a35c8 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 06:20:11 +0200 Subject: [PATCH 002/182] Add gpu_repr(uniform) to layout example --- examples/hello_triangles/src/util/shame_glam.rs | 6 +++--- examples/type_layout/src/main.rs | 13 ++++++++++++- shame/src/frontend/rust_types/type_layout/mod.rs | 10 +++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index 6cf3441..7f85092 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout().into() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - sm::f32x2::gpu_layout().into() + sm::f32x2::gpu_layout() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout().into() } + fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 2b37fdf..ea0f64a 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -97,7 +97,18 @@ fn main() { f32x1::layout_type_sized(), ); let layout = TypeLayout::new_storage_layout_for(sized_struct); - assert!(layout.byte_align.as_u32() == 16); + assert!(layout.align.as_u32() == 16); + + // gpu_repr(uniform) and gpu_repr(storage), which is the default, are now supported + #[derive(shame::GpuLayout)] + #[gpu_repr(uniform)] + struct D { + a: f32x2, + b: Array, + } + + // this would be 8 for storage layout rules + assert!(D::gpu_layout().align.as_u32() == 16); // Let's end on a pretty error message let mut sized_struct = cs::SizedStruct::new("D", "a", f32x2::layout_type_sized()) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index bc4a432..35c6f26 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -119,7 +119,7 @@ pub struct TypeLayout { /// the byte alignment /// /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) - pub byte_align: U32PowerOf2, + pub align: U32PowerOf2, /// the type contained in the bytes of this type layout pub kind: TypeLayoutSemantics, @@ -189,7 +189,7 @@ impl TypeLayout { ) -> Self { TypeLayout { byte_size, - byte_align, + align: byte_align, kind, cpu_shareable: hostshareable, _phantom: PhantomData, @@ -206,7 +206,7 @@ pub(in super::super::rust_types) mod type_layout_internal { pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { TypeLayout { byte_size: layout.byte_size, - byte_align: layout.byte_align, + align: layout.align, kind: layout.kind, cpu_shareable: layout.cpu_shareable, _phantom: PhantomData, @@ -392,7 +392,7 @@ impl TypeLayout { pub fn byte_size(&self) -> Option { self.byte_size } /// Align of the represented type. - pub fn byte_align(&self) -> U32PowerOf2 { self.byte_align } + pub fn byte_align(&self) -> U32PowerOf2 { self.align } /// Although all TypeLayout always implement Into, this method /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. @@ -479,7 +479,7 @@ impl TypeLayout { } } write!(f, "{indent}}}")?; - write!(f, " align={}", self.byte_align.as_u64())?; + write!(f, " align={}", self.align.as_u64())?; if let Some(size) = self.byte_size { write!(f, " size={size}")?; } else { From 2a9a4ed96efa10789c10115e94d2e0239d7abe10 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 06:44:59 +0200 Subject: [PATCH 003/182] Add GpuStore: GpuAligned back --- shame/src/frontend/rust_types/type_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 7d01d4e..78ff297 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -65,7 +65,7 @@ pub struct BindingArgs { //[old-doc] //[old-doc] corresponds to WGSL "Storable type" https://www.w3.org/TR/WGSL/#storable-types /// (no documentation yet) -pub trait GpuStore: GetAllFields + FromAnys { +pub trait GpuStore: GpuAligned + GetAllFields + FromAnys { /// the type whose public immutable interface is exposed by [`shame::Ref`]: /// /// ` as std::ops::Deref>::Target` From 4619d603539a2bd604422f91951d3bca7ae401c7 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 07:04:40 +0200 Subject: [PATCH 004/182] Cleanup type layout Vertex --- examples/type_layout/src/main.rs | 27 +++-- .../{storage_uniform.rs => builder.rs} | 19 +++- .../frontend/rust_types/type_layout/mod.rs | 10 +- .../frontend/rust_types/type_layout/vertex.rs | 100 ------------------ 4 files changed, 38 insertions(+), 118 deletions(-) rename shame/src/frontend/rust_types/type_layout/{storage_uniform.rs => builder.rs} (95%) delete mode 100644 shame/src/frontend/rust_types/type_layout/vertex.rs diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index ea0f64a..7dda41d 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -6,26 +6,35 @@ use shame::{ boolx1, cpu_shareable::{self as cs, BinaryReprSized}, f32x1, f32x2, f32x3, f32x4, - type_layout::*, + type_layout::{ + constraint::{Packed, Plain, Storage}, + *, + }, Array, GpuLayout, GpuSized, VertexAttribute, VertexLayout, }; fn main() { - // We'll start by building a `TypeLayout`, which can be used for ... nothing really + // We'll start by building a `TypeLayout`, which for this Vertex type. #[derive(GpuLayout)] struct Vertex { position: f32x3, normal: f32x3, - uv: f32x1, + uv: f32x2, } - // TypeLayout::vertex_builder immediately takes the first field of the struct, because + // SizedStruct::new immediately takes the first field of the struct, because // structs need to have at least one field. - let mut builder = - TypeLayout::vertex_builder("Vertex", "position", f32x4::vertex_attrib_format(), cs::Repr::Storage) - .extend("normal", f32x3::vertex_attrib_format()) - .extend("uv", f32x1::vertex_attrib_format()) - .finish(); + let sized_struct = cs::SizedStruct::new("Vertex", "position", f32x3::layout_type_sized()) + .extend("normal", f32x4::layout_type_sized()) + .extend("uv", f32x1::layout_type_sized()); + // A layout that follows the storage layout rules + let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), cs::Repr::Storage); + // Or we can get a TypeLayout, which guarantees the storage layout rules. + let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); + assert_eq!(layout, layout_storage); + // Or we get it as a packed layout + let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); + assert_ne!(layout_storage, layout_packed); // Now we'll replicate the layout of this struct diff --git a/shame/src/frontend/rust_types/type_layout/storage_uniform.rs b/shame/src/frontend/rust_types/type_layout/builder.rs similarity index 95% rename from shame/src/frontend/rust_types/type_layout/storage_uniform.rs rename to shame/src/frontend/rust_types/type_layout/builder.rs index d788a71..0af94d4 100644 --- a/shame/src/frontend/rust_types/type_layout/storage_uniform.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -6,6 +6,19 @@ use crate::{ use super::*; use TypeLayoutSemantics as TLS; +impl TypeLayout { + pub fn new_storage_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) + } + + /// Get the `CpuShareableType` this layout is based on. + pub fn cpu_shareable(&self) -> &LayoutType { + self.cpu_shareable + .as_ref() + .expect("constraint::Storage always contains a cpu-shareable") + } +} + impl TypeLayout { /// Takes a `SizedType`, because wgsl only supports sized uniform buffers. /// Use `new_layout_for_unchecked`to obtain the the uniform layout of an unsized host-shareable. /// Using the unsized layout with wgsl as your target language will cause an error. @@ -21,9 +34,9 @@ impl TypeLayout { } } -impl TypeLayout { - pub fn new_storage_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) +impl TypeLayout { + pub fn new_packed_layout_for(ty: impl Into) -> Self { + type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Packed)) } /// Get the `CpuShareableType` this layout is based on. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 35c6f26..8c49ad0 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -24,13 +24,11 @@ use crate::{ use cpu_shareable::{LayoutType, Matrix, Vector}; use thiserror::Error; +mod builder; pub mod cpu_shareable; mod eq; -mod storage_uniform; -mod vertex; -pub use vertex::*; -pub use storage_uniform::*; +pub use builder::*; pub(crate) use eq::*; /// The type contained in the bytes of a `TypeLayout`. @@ -163,7 +161,7 @@ pub mod constraint { }; } - type_restriction!(Plain, Storage, Uniform, Vertex); + type_restriction!(Plain, Storage, Uniform, Packed); macro_rules! impl_from_into { ($from:ident -> $($into:ident),*) => { @@ -177,7 +175,7 @@ pub mod constraint { impl_from_into!(Storage -> Plain); impl_from_into!(Uniform -> Plain, Storage); - impl_from_into!(Vertex -> Plain); + impl_from_into!(Packed -> Plain); } impl TypeLayout { diff --git a/shame/src/frontend/rust_types/type_layout/vertex.rs b/shame/src/frontend/rust_types/type_layout/vertex.rs deleted file mode 100644 index 3fe905e..0000000 --- a/shame/src/frontend/rust_types/type_layout/vertex.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{any::VertexAttribFormat, cpu_shareable::Repr}; -use super::{*}; - -impl TypeLayout { - pub fn from_vertex_attribute(attribute: VertexAttribFormat) -> TypeLayout { - let (size, align, kind) = match attribute { - VertexAttribFormat::Fine(len, scalar) => { - let sized = cpu_shareable::Vector::new(scalar, len); - (sized.byte_size(), sized.align(), TypeLayoutSemantics::Vector(sized)) - } - VertexAttribFormat::Coarse(packed) => ( - u8::from(packed.byte_size()) as u64, - U32PowerOf2::try_from(packed.align() as u32).unwrap(), - TypeLayoutSemantics::PackedVector(packed), - ), - }; - - type_layout_internal::cast_unchecked(TypeLayout::new(Some(size), align, kind, None)) - } - - /// Creates a new builder for a `TypeLayout`. Takes the first - /// attribute immediately, because at least one attribut needs to exist. - /// - /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` - /// are equivalent for vertex layouts. - pub fn vertex_builder( - struct_name: impl Into, - field_options: impl Into, - attribute: VertexAttribFormat, - rules: Repr, - ) -> VertexLayoutBuilder { - VertexLayoutBuilder::new(struct_name, field_options, attribute, rules) - } -} - -pub struct VertexLayoutBuilder { - name: CanonName, - attributes: Vec, - rules: Repr, -} - -impl VertexLayoutBuilder { - /// Creates a new builder for a `TypeLayout`. Takes the first - /// attribute immediately, because at least one attribut needs to exist. - /// - /// `rules` determines whether the layout is packed or not. The `Uniform` and `Storage` - /// are equivalent for vertex layouts. - pub fn new( - struct_name: impl Into, - field_options: impl Into, - attribute: VertexAttribFormat, - rules: Repr, - ) -> Self { - let this = VertexLayoutBuilder { - name: struct_name.into(), - attributes: Vec::new(), - rules, - }; - this.extend(field_options, attribute) - } - - pub fn extend(mut self, field_options: impl Into, attribute: VertexAttribFormat) -> Self { - let layout = TypeLayout::from_vertex_attribute(attribute); - let options = field_options.into(); - self.attributes.push(FieldLayout::new( - options.name, - options.custom_min_size, - options.custom_min_align, - layout.into(), - )); - self - } - - pub fn finish(self) -> TypeLayout { - let mut calc = LayoutCalculator::new(matches!(self.rules, Repr::Packed)); - let fields = self - .attributes - .into_iter() - .map(|field| { - let rel_byte_offset = calc.extend( - field.byte_size().unwrap(), // attributes are always sized - field.byte_align(), - *field.custom_min_size, - *field.custom_min_align, - ); - FieldLayoutWithOffset { field, rel_byte_offset } - }) - .collect::>(); - - type_layout_internal::cast_unchecked(TypeLayout::new( - Some(calc.byte_size()), - calc.align(), - TypeLayoutSemantics::Structure(Rc::new(StructLayout { - name: self.name.into(), - fields, - })), - None, - )) - } -} From fb24c94070ca58921fa3e919bef4a32ffa526ae3 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 22 May 2025 07:07:30 +0200 Subject: [PATCH 005/182] Fix example --- examples/type_layout/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 7dda41d..d07064e 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -25,7 +25,7 @@ fn main() { // SizedStruct::new immediately takes the first field of the struct, because // structs need to have at least one field. let sized_struct = cs::SizedStruct::new("Vertex", "position", f32x3::layout_type_sized()) - .extend("normal", f32x4::layout_type_sized()) + .extend("normal", f32x3::layout_type_sized()) .extend("uv", f32x1::layout_type_sized()); // A layout that follows the storage layout rules let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), cs::Repr::Storage); From 82228640d0a47a1762bbb0ff92e1f8504fdf8c84 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 03:40:50 +0200 Subject: [PATCH 006/182] Rebase --- examples/shame_wgpu/src/conversion.rs | 2 +- examples/type_layout/src/main.rs | 59 ++--- shame/src/common/proc_macro_reexports.rs | 17 +- shame/src/common/proc_macro_utils.rs | 4 +- shame/src/frontend/any/render_io.rs | 7 +- shame/src/frontend/encoding/buffer.rs | 4 +- shame/src/frontend/rust_types/array.rs | 28 +-- shame/src/frontend/rust_types/atomic.rs | 23 +- .../src/frontend/rust_types/layout_traits.rs | 48 ++-- shame/src/frontend/rust_types/mat.rs | 19 +- shame/src/frontend/rust_types/packed_vec.rs | 24 +- shame/src/frontend/rust_types/struct_.rs | 16 +- .../rust_types/type_layout/builder.rs | 181 +++++++------- .../src/frontend/rust_types/type_layout/eq.rs | 14 +- .../align_size.rs | 63 +++-- .../{cpu_shareable => layoutable}/builder.rs | 71 +++--- .../{cpu_shareable => layoutable}/mod.rs | 203 +++++++++------- .../frontend/rust_types/type_layout/mod.rs | 227 +++++++----------- shame/src/frontend/rust_types/type_traits.rs | 1 - shame/src/frontend/rust_types/vec.rs | 20 +- shame/src/ir/ir_type/layout_constraints.rs | 6 +- shame/src/ir/ir_type/tensor.rs | 1 + shame/src/ir/ir_type/ty.rs | 2 +- shame/src/ir/pipeline/wip_pipeline.rs | 8 +- shame/src/lib.rs | 59 ++++- shame/tests/test_layout.rs | 4 +- shame_derive/src/derive_layout.rs | 42 ++-- 27 files changed, 617 insertions(+), 536 deletions(-) rename shame/src/frontend/rust_types/type_layout/{cpu_shareable => layoutable}/align_size.rs (81%) rename shame/src/frontend/rust_types/type_layout/{cpu_shareable => layoutable}/builder.rs (70%) rename shame/src/frontend/rust_types/type_layout/{cpu_shareable => layoutable}/mod.rs (64%) diff --git a/examples/shame_wgpu/src/conversion.rs b/examples/shame_wgpu/src/conversion.rs index 05b6d73..3bb0ff7 100644 --- a/examples/shame_wgpu/src/conversion.rs +++ b/examples/shame_wgpu/src/conversion.rs @@ -449,7 +449,7 @@ fn color_writes(write_mask: smr::ChannelWrites) -> wgpu::ColorWrites { #[rustfmt::skip] fn vertex_format(format: smr::VertexAttribFormat) -> Result { - use shame::cpu_shareable::ScalarType as S; + use shame::any::layout::ScalarType as S; use smr::Len as L; use wgpu::VertexFormat as W; let unsupported = Err(ShameToWgpuError::UnsupportedVertexAttribFormat(format)); diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index d07064e..56bfbc0 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -1,16 +1,17 @@ #![allow(dead_code, unused)] //! Demonstration of the TypeLayout and TypeLayout Builder API. +use layout::{repr, Repr, SizedStruct}; use shame::{ - any::{self, U32PowerOf2}, - boolx1, - cpu_shareable::{self as cs, BinaryReprSized}, - f32x1, f32x2, f32x3, f32x4, - type_layout::{ - constraint::{Packed, Plain, Storage}, - *, + any::{ + self, + layout::{ + self, LayoutableSized, UnsizedStruct, SizedField, SizedType, ScalarType, Len, RuntimeSizedArrayField, + FieldOptions, Vector, + }, + U32PowerOf2, }, - Array, GpuLayout, GpuSized, VertexAttribute, VertexLayout, + boolx1, f32x1, f32x2, f32x3, f32x4, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, VertexLayout, }; fn main() { @@ -24,16 +25,16 @@ fn main() { // SizedStruct::new immediately takes the first field of the struct, because // structs need to have at least one field. - let sized_struct = cs::SizedStruct::new("Vertex", "position", f32x3::layout_type_sized()) - .extend("normal", f32x3::layout_type_sized()) - .extend("uv", f32x1::layout_type_sized()); + let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) + .extend("normal", f32x3::layoutable_type_sized()) + .extend("uv", f32x1::layoutable_type_sized()); // A layout that follows the storage layout rules - let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), cs::Repr::Storage); + let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), Repr::Storage); // Or we can get a TypeLayout, which guarantees the storage layout rules. - let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); + let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); assert_eq!(layout, layout_storage); // Or we get it as a packed layout - let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); + let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); assert_ne!(layout_storage, layout_packed); @@ -45,16 +46,16 @@ fn main() { c: Array, } - // Be default structs are #[gpu_repr(Storage)], which means that it follows + // By default structs are #[gpu_repr(Storage)], which means that it follows // the wgsl storage layout rules (std430). To obtain a corresponding TypeLayout // we first need to build a `CpuShareableType`, in our case an `UnsizedStruct`. - let unsized_struct = cs::UnsizedStruct { + let unsized_struct = UnsizedStruct { name: "A".into(), sized_fields: vec![ - cs::SizedField::new("a", cs::Vector::new(cs::ScalarType::F32, cs::Len::X4)), - cs::SizedField::new("b", f32x3::layout_type_sized()), + SizedField::new("a", Vector::new(ScalarType::F32, Len::X4)), + SizedField::new("b", f32x3::layoutable_type_sized()), ], - last_unsized: cs::RuntimeSizedArrayField::new("c", None, f32x1::layout_type_sized()), + last_unsized: RuntimeSizedArrayField::new("c", None, f32x1::layoutable_type_sized()), }; // And now we can get the `TypeLayout`. let s_layout = TypeLayout::new_storage_layout_for(unsized_struct.clone()); @@ -75,9 +76,9 @@ fn main() { } // Sized structs require a builder to ensure it always contains at least one field. - let mut sized_struct = cs::SizedStruct::new("B", "b", f32x4::layout_type_sized()) - .extend("b", f32x3::layout_type_sized()) - .extend("c", f32x1::layout_type_sized()); + let mut sized_struct = SizedStruct::new("B", "b", f32x4::layoutable_type_sized()) + .extend("b", f32x3::layoutable_type_sized()) + .extend("c", f32x1::layoutable_type_sized()); // Since this struct is sized we can use TypeLayout::::new_layout_for. let u_layout = TypeLayout::new_uniform_layout_for(sized_struct.clone()); let s_layout = TypeLayout::new_storage_layout_for(sized_struct); @@ -87,7 +88,7 @@ fn main() { // uniform layout rules despite not being `TypeLayout`, // which in this case will succeed, but if it doesn't we get a very nice error message about // why the layout is not compatible with the uniform layout rules. - let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); + let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); // Let's replicate a more complex example with explicit field size and align. #[derive(shame::GpuLayout)] @@ -99,11 +100,11 @@ fn main() { c: f32x2, } - let mut sized_struct = cs::SizedStruct::new("C", "a", f32x3::layout_type_sized()) - .extend(FieldOptions::new("b", None, Some(16)), f32x3::layout_type_sized()) + let mut sized_struct = SizedStruct::new("C", "a", f32x3::layoutable_type_sized()) + .extend(FieldOptions::new("b", None, Some(16)), f32x3::layoutable_type_sized()) .extend( FieldOptions::new("c", Some(U32PowerOf2::_16), None), - f32x1::layout_type_sized(), + f32x1::layoutable_type_sized(), ); let layout = TypeLayout::new_storage_layout_for(sized_struct); assert!(layout.align.as_u32() == 16); @@ -120,11 +121,11 @@ fn main() { assert!(D::gpu_layout().align.as_u32() == 16); // Let's end on a pretty error message - let mut sized_struct = cs::SizedStruct::new("D", "a", f32x2::layout_type_sized()) + let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) // This has align of 4 for storage and align of 16 for uniform. - .extend("b", Array::>::layout_type_sized()); + .extend("b", Array::>::layoutable_type_sized()); let s_layout = TypeLayout::new_storage_layout_for(sized_struct); - let result = TypeLayout::::try_from(&s_layout); + let result = TypeLayout::::try_from(&s_layout); match result { Err(e) => println!("This error is a showcase:\n{}", e), Ok(u_layout) => println!("It unexpectedly worked, ohh no."), diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 86d2f64..b78acee 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -27,16 +27,8 @@ pub use crate::frontend::rust_types::type_layout::FieldLayout; pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; pub use crate::frontend::rust_types::type_layout::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedStruct; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryRepr; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::BinaryReprSized; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::LayoutType; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedType; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::SizedOrArray; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::Repr; -pub use crate::frontend::rust_types::type_layout::cpu_shareable::StructFromPartsError; +pub use crate::frontend::rust_types::type_layout::Repr; pub use crate::frontend::rust_types::type_layout::TypeLayout; -pub use crate::frontend::rust_types::type_layout::TypeLayoutRules; pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; pub use crate::frontend::rust_types::type_traits::BindingArgs; pub use crate::frontend::rust_types::type_traits::GpuAligned; @@ -48,6 +40,13 @@ pub use crate::frontend::rust_types::type_traits::NoBools; pub use crate::frontend::rust_types::type_traits::NoHandles; pub use crate::frontend::rust_types::type_traits::VertexAttribute; pub use crate::frontend::rust_types::type_traits::GpuLayoutField; +pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; +pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; +pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; +pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; +pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; +pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; +pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; pub use crate::frontend::rust_types::AsAny; pub use crate::frontend::rust_types::GpuType; #[allow(missing_docs)] diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 7e22a09..85f0d19 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -5,9 +5,7 @@ use crate::{ any::{Any, InvalidReason}, rust_types::{ error::FrontendError, - type_layout::{ - FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutRules, TypeLayoutSemantics, - }, + type_layout::{FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics}, }, }, ir::{ diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index a963506..4b267f1 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,10 +2,9 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; -use crate::cpu_shareable; +use crate::any::layout; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::TypeLayout; -use crate::type_layout::cpu_shareable::array_stride; use crate::{ call_info, common::iterator_ext::try_collect, @@ -110,7 +109,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, cpu_shareable::ScalarType), + Fine(Len, layout::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -253,7 +252,7 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - array_stride(layout.byte_align(), size) + layout::array_stride(layout.align(), size) }; use TypeLayoutSemantics as TLS; diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index ee857c4..c6f3c1c 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -1,3 +1,4 @@ +use crate::any::layout::LayoutableSized; use crate::common::proc_macro_reexports::GpuStoreImplCategory; use crate::frontend::any::shared_io::{BindPath, BindingType, BufferBindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -17,7 +18,6 @@ use crate::frontend::rust_types::{reference::AccessModeReadable, scalar_type::Sc use crate::ir::pipeline::StageMask; use crate::ir::recording::{Context, MemoryRegion}; use crate::ir::Type; -use crate::type_layout::cpu_shareable::BinaryReprSized; use crate::{self as shame, call_info, ir, GpuLayout}; use std::borrow::Borrow; @@ -319,7 +319,7 @@ fn store_type_from_impl_category(category: GpuStoreImplCategory) -> ir::StoreTyp #[rustfmt::skip] impl Binding for Buffer, AS, DYN_OFFSET> where - T: GpuType + GpuSized + GpuLayout + BinaryReprSized + T: GpuType + GpuSized + GpuLayout + LayoutableSized { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 644d10a..cf49295 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,15 +5,15 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{ElementLayout, TypeLayout, TypeLayoutRules, TypeLayoutSemantics}; +use super::type_layout::{self, layoutable, ElementLayout, TypeLayout, TypeLayoutSemantics}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; +use crate::any::layout::{Layoutable, LayoutableSized}; use crate::common::small_vec::SmallVec; -use crate::cpu_shareable::{BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; use crate::frontend::any::InvalidReason; @@ -25,7 +25,7 @@ use crate::frontend::rust_types::vec::vec; use crate::ir::ir_type::stride_of_array_from_element_align_size; use crate::ir::pipeline::StageMask; use crate::ir::recording::Context; -use crate::{call_info, cpu_shareable, for_count, ir}; +use crate::{call_info, for_count, ir}; use std::borrow::Cow; use std::marker::PhantomData; use std::num::NonZeroU32; @@ -156,16 +156,16 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} -impl BinaryReprSized for Array> { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::SizedArray::new(T::layout_type_sized(), Size::::nonzero()).into() +impl LayoutableSized for Array> { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::SizedArray::new(T::layoutable_type_sized(), Size::::nonzero()).into() } } -impl BinaryRepr for Array { - fn layout_type() -> cpu_shareable::LayoutType { +impl Layoutable for Array { + fn layoutable_type() -> layoutable::LayoutableType { match N::LEN { - Some(n) => cpu_shareable::SizedArray::new(T::layout_type_sized(), n).into(), - None => cpu_shareable::RuntimeSizedArray::new(T::layout_type_sized()).into(), + Some(n) => layoutable::SizedArray::new(T::layoutable_type_sized(), n).into(), + None => layoutable::RuntimeSizedArray::new(T::layoutable_type_sized()).into(), } } } @@ -178,8 +178,8 @@ impl ToGpuType for Array { fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { Some(self) } } -impl GpuLayout for Array { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } +impl GpuLayout for Array { + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -199,10 +199,10 @@ impl GpuLayout name.into(), TypeLayout::new( N::LEN.map(|n| n.get() as u64 * t_cpu_size), - t_cpu_layout.byte_align(), + t_cpu_layout.align(), TypeLayoutSemantics::Array( Rc::new(ElementLayout { - byte_stride: cpu_shareable::array_stride(t_cpu_layout.byte_align(), t_cpu_size), + byte_stride: layoutable::array_stride(t_cpu_layout.align(), t_cpu_size), ty: t_cpu_layout, }), N::LEN.map(NonZeroU32::get), diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index cb239b8..0a6ff26 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -6,7 +6,11 @@ use super::{ mem::{AddressSpace, AddressSpaceAtomic}, reference::{AccessMode, AccessModeReadable, ReadWrite}, scalar_type::{ScalarType, ScalarTypeInteger}, - type_layout::{cpu_shareable::BinaryReprSized, TypeLayout, TypeLayoutRules}, + type_layout::{ + self, + layoutable::{self, LayoutableSized}, + TypeLayout, + }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -14,10 +18,7 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::{ - cpu_shareable::{self, BinaryRepr}, - frontend::rust_types::reference::Ref, -}; +use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref}; use crate::{ boolx1, frontend::{ @@ -132,20 +133,20 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } -impl BinaryReprSized for Atomic { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::Atomic { +impl LayoutableSized for Atomic { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::Atomic { scalar: T::SCALAR_TYPE_INTEGER, } .into() } } -impl BinaryRepr for Atomic { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for Atomic { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for Atomic { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 0b10946..3a4741c 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,7 +1,7 @@ +use crate::any::layout::{Layoutable, LayoutableSized, Repr}; use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; -use crate::cpu_shareable::{BinaryRepr, BinaryReprSized, Repr}; use crate::frontend::any::render_io::{ Attrib, VertexBufferLookupIndex, Location, VertexAttribFormat, VertexBufferLayout, VertexLayoutError, }; @@ -22,11 +22,10 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::constraint::TypeConstraint; -use super::type_layout::cpu_shareable::{self, array_stride, Vector}; +use super::type_layout::repr::TypeRepr; +use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ - self, constraint, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutError, - TypeLayoutRules, TypeLayoutSemantics, + self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -138,7 +137,7 @@ use std::rc::Rc; /// [`Texture`]: crate::Texture /// [`StorageTexture`]: crate::StorageTexture /// -pub trait GpuLayout: BinaryRepr { +pub trait GpuLayout: Layoutable { /// returns a [`TypeLayout`] object that can be used to inspect the layout /// of a type on the gpu. /// @@ -159,9 +158,9 @@ pub trait GpuLayout: BinaryRepr { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` - fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layout_type(), Self::gpu_repr()) } + fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layoutable_type(), Self::gpu_repr()) } - /// TODO(chronicl) docs + /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. fn gpu_repr() -> Repr; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a @@ -207,7 +206,7 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, cpu_layout: &TypeLayout, @@ -215,7 +214,7 @@ pub(crate) fn check_layout_push_error( skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { - type_layout::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) + type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { if skip_stride_check { @@ -228,8 +227,8 @@ pub(crate) fn check_layout_push_error( name: gpu_layout.short_name(), }), (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = array_stride(cpu_layout.byte_align(), cpu_size); - let gpu_stride = array_stride(gpu_layout.byte_align(), gpu_size); + let cpu_stride = array_stride(cpu_layout.align(), cpu_size); + let gpu_stride = array_stride(gpu_layout.align(), gpu_size); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { @@ -354,7 +353,6 @@ pub(crate) fn from_single_any(mut anys: impl Iterator) -> Any { pub trait VertexLayout: GpuLayout + FromAnys {} impl VertexLayout for T {} -// TODO(chronicl) uncomment // #[derive(GpuLayout)] // TODO(release) remove this type. This is an example impl for figuring out how the derive macro should work #[derive(Clone)] @@ -540,11 +538,11 @@ where } } -impl BinaryReprSized for GpuT { - fn layout_type_sized() -> cpu_shareable::SizedType { todo!() } +impl LayoutableSized for GpuT { + fn layoutable_type_sized() -> layoutable::SizedType { todo!() } } -impl BinaryRepr for GpuT { - fn layout_type() -> cpu_shareable::LayoutType { todo!() } +impl Layoutable for GpuT { + fn layoutable_type() -> layoutable::LayoutableType { todo!() } } impl GpuLayout for GpuT { @@ -696,8 +694,8 @@ where impl CpuLayout for f32 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::F32, - cpu_shareable::Len::X1, + layoutable::ScalarType::F32, + layoutable::Len::X1, ))) } // fn gpu_type_layout() -> Option> { @@ -708,8 +706,8 @@ impl CpuLayout for f32 { impl CpuLayout for f64 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::F64, - cpu_shareable::Len::X1, + layoutable::ScalarType::F64, + layoutable::Len::X1, ))) } } @@ -717,8 +715,8 @@ impl CpuLayout for f64 { impl CpuLayout for u32 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::U32, - cpu_shareable::Len::X1, + layoutable::ScalarType::U32, + layoutable::Len::X1, ))) } } @@ -726,8 +724,8 @@ impl CpuLayout for u32 { impl CpuLayout for i32 { fn cpu_layout() -> TypeLayout { TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - cpu_shareable::ScalarType::I32, - cpu_shareable::Len::X1, + layoutable::ScalarType::I32, + layoutable::Len::X1, ))) } } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index ded204c..4795935 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -8,8 +8,9 @@ use super::{ reference::{AccessMode, AccessModeReadable}, scalar_type::{ScalarType, ScalarTypeFp}, type_layout::{ - cpu_shareable::{self, BinaryReprSized}, - TypeLayout, TypeLayoutRules, + self, + layoutable::{self, LayoutableSized}, + TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, @@ -18,7 +19,7 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{cpu_shareable::BinaryRepr, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; +use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; use crate::{ call_info, frontend::{ @@ -53,9 +54,9 @@ impl Default for mat { } } -impl BinaryReprSized for mat { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::Matrix { +impl LayoutableSized for mat { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::Matrix { columns: C::LEN2, rows: R::LEN2, scalar: T::SCALAR_TYPE_FP, @@ -63,12 +64,12 @@ impl BinaryReprSized for mat { .into() } } -impl BinaryRepr for mat { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for mat { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for mat { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 148c98e..8682fe9 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -5,9 +5,11 @@ use std::{ }; use crate::{ - any::{AsAny, DataPackingFn}, + any::{ + layout::{Layoutable, LayoutableSized}, + AsAny, DataPackingFn, + }, common::floating_point::f16, - cpu_shareable::{BinaryRepr, BinaryReprSized}, f32x2, f32x4, i32x4, u32x1, u32x4, }; use crate::frontend::rust_types::len::{x1, x2, x3, x4}; @@ -23,7 +25,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{cpu_shareable, type_layout_internal, TypeLayout, TypeLayoutRules, TypeLayoutSemantics}, + type_layout::{self, layoutable, type_layout_internal, TypeLayout, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -131,9 +133,9 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} -impl BinaryReprSized for PackedVec { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::PackedVector { +impl LayoutableSized for PackedVec { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::PackedVector { scalar_type: T::SCALAR_TYPE, bits_per_component: T::BITS_PER_COMPONENT, len: L::LEN_EVEN, @@ -141,17 +143,17 @@ impl BinaryReprSized for PackedVec { .into() } } -impl BinaryRepr for PackedVec { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for PackedVec { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for PackedVec { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty = Self::sized_ty_equivalent(); let name = sized_ty.to_string().into(); - let sized_ty: cpu_shareable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); + let sized_ty: layoutable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); let layout = TypeLayout::new_storage_layout_for(sized_ty); Some(Ok((name, layout.into()))) } @@ -170,7 +172,7 @@ impl From for PackedVec { let inner = Context::try_with(call_info!(), |ctx| { let err = |ty| { ctx.push_error_get_invalid_any( - FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout().into()).into(), + FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout()).into(), ) }; match any.ty() { diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index 4fec66f..14e4c6e 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,5 +1,5 @@ +use crate::any::layout::{Layoutable, LayoutableSized}; use crate::common::small_vec::SmallVec; -use crate::cpu_shareable::{self, BinaryRepr, BinaryReprSized}; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; use crate::frontend::encoding::buffer::BufferRefInner; @@ -23,7 +23,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::{TypeLayout, TypeLayoutRules}; +use super::type_layout::{self, layoutable, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -135,19 +135,19 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl BinaryReprSized for Struct { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::SizedStruct::try_from(T::get_sizedstruct_type()) +impl LayoutableSized for Struct { + fn layoutable_type_sized() -> layoutable::SizedType { + layoutable::SizedStruct::try_from(T::get_sizedstruct_type()) .expect("no bools") .into() } } -impl BinaryRepr for Struct { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } +impl Layoutable for Struct { + fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for Struct { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index 0af94d4..a3bfd53 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -1,71 +1,81 @@ -use crate::{ - __private::SmallVec, - cpu_shareable::{self as cs, Major, Repr, SizedField, UnsizedStruct}, - ir::{ir_type::max_u64_po2_dividing, StoreType}, +use crate::ir::{ir_type::max_u64_po2_dividing, StoreType}; +use super::{ + layoutable::{ + MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, UnsizedStruct, + }, + *, }; -use super::*; use TypeLayoutSemantics as TLS; -impl TypeLayout { - pub fn new_storage_layout_for(ty: impl Into) -> Self { +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to wgsl storage layout rules (std430). + pub fn new_storage_layout_for(ty: impl Into) -> Self { type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) } - /// Get the `CpuShareableType` this layout is based on. - pub fn cpu_shareable(&self) -> &LayoutType { - self.cpu_shareable + /// Get the `LayoutableType` this layout is based on. + pub fn layoutable_type(&self) -> &LayoutableType { + self.layoutable_type .as_ref() - .expect("constraint::Storage always contains a cpu-shareable") + .expect("Storage is always based on a layoutable type") } } -impl TypeLayout { - /// Takes a `SizedType`, because wgsl only supports sized uniform buffers. - /// Use `new_layout_for_unchecked`to obtain the the uniform layout of an unsized host-shareable. /// Using the unsized layout with wgsl as your target language will cause an error. - pub fn new_uniform_layout_for(ty: impl Into) -> Self { +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to wgsl uniform layout rules (std140). + pub fn new_uniform_layout_for(ty: impl Into) -> Self { type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Uniform)) } - /// Get the `CpuShareableType` this layout is based on. - pub fn cpu_shareable(&self) -> &LayoutType { - self.cpu_shareable + /// Get the `LayoutableType` this layout is based on. + pub fn layoutable_type(&self) -> &LayoutableType { + self.layoutable_type .as_ref() - .expect("constraint::Uniform always contains a cpu-shareable") + .expect("Uniform is always based on a layoutable type") } } -impl TypeLayout { - pub fn new_packed_layout_for(ty: impl Into) -> Self { +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` in packed format. + pub fn new_packed_layout_for(ty: impl Into) -> Self { type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Packed)) } - /// Get the `CpuShareableType` this layout is based on. - pub fn cpu_shareable(&self) -> &LayoutType { - self.cpu_shareable + /// Get the `LayoutableType` this layout is based on. + pub fn layoutable_type(&self) -> &LayoutableType { + self.layoutable_type .as_ref() - .expect("constraint::Storage always contains a cpu-shareable") + .expect("Packed is always based on a layoutable type") } } impl TypeLayout { - pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to the given `repr`. + pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { match ty.into() { - LayoutType::Sized(ty) => Self::from_sized_type(ty, repr), - LayoutType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), - LayoutType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), + LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), + LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), + LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), } } - fn from_sized_type(ty: cs::SizedType, repr: Repr) -> Self { + fn from_sized_type(ty: SizedType, repr: Repr) -> Self { let (size, align, tls) = match &ty { - cs::SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), - cs::SizedType::Atomic(a) => ( + SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), + SizedType::Atomic(a) => ( a.byte_size(), a.align(), TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), ), - cs::SizedType::Matrix(m) => (m.byte_size(Major::Row), m.align(repr, Major::Row), TLS::Matrix(*m)), - cs::SizedType::Array(a) => ( + SizedType::Matrix(m) => ( + m.byte_size(MatrixMajor::Row), + m.align(repr, MatrixMajor::Row), + TLS::Matrix(*m), + ), + SizedType::Array(a) => ( a.byte_size(repr), a.align(repr), TLS::Array( @@ -76,8 +86,8 @@ impl TypeLayout { Some(a.len.get()), ), ), - cs::SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), - cs::SizedType::Struct(s) => { + SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), + SizedType::Struct(s) => { let mut field_offsets = s.field_offsets(repr); let fields = (&mut field_offsets) .zip(s.fields()) @@ -128,7 +138,7 @@ impl TypeLayout { } - fn from_runtime_sized_array(ty: cs::RuntimeSizedArray, repr: Repr) -> Self { + fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { Self::new( None, ty.byte_align(repr), @@ -156,44 +166,43 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F } } -impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { +impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { type Error = UniformLayoutError; - fn try_from(s_layout: &'a TypeLayout) -> Result { - let cpu_shareable = s_layout.cpu_shareable().clone(); + fn try_from(s_layout: &'a TypeLayout) -> Result { + let layoutable_type = s_layout.layoutable_type().clone(); // Checking whether it's sized - let sized = match cpu_shareable { - LayoutType::Sized(sized) => sized, + let sized = match layoutable_type { + LayoutableType::Sized(sized) => sized, _ => { - return Err(UniformLayoutError::MustBeSized("wgsl", cpu_shareable.into())); + return Err(UniformLayoutError::MustBeSized("wgsl", layoutable_type)); } }; let u_layout = TypeLayout::new_uniform_layout_for(sized); // Checking fields - fn check_layout( + fn check_layout( s_layout: &TypeLayout, u_layout: &TypeLayout, is_top_level: bool, ) -> Result<(), Mismatch> { - // kinds are the same, because the type layouts are based on the same cpu shareable + // kinds are the same, because the type layouts are based on the same LayoutableType match (&s_layout.kind, &u_layout.kind) { (TLS::Structure(s), TLS::Structure(u)) => { for (i, (s_field, u_field)) in s.fields.iter().zip(u.fields.iter()).enumerate() { // Checking field offset if s_field.rel_byte_offset != u_field.rel_byte_offset { - let (sized_fields, last_unsized) = match s_layout.get_cpu_shareable().unwrap() { - LayoutType::Sized(cs::SizedType::Struct(s)) => (s.fields().to_vec(), None), - LayoutType::UnsizedStruct(s) => (s.sized_fields.clone(), Some(s.last_unsized.clone())), + let struct_type = match s_layout.get_layoutable_type().unwrap() { + LayoutableType::Sized(SizedType::Struct(s)) => StructKind::Sized(s.clone()), + LayoutableType::UnsizedStruct(s) => StructKind::Unsized(s.clone()), _ => unreachable!("is struct, because tls is struct"), }; - return Err(Mismatch::StructureFieldOffset(StructureFieldOffsetError { + return Err(Mismatch::StructureFieldOffset(StructFieldOffsetError { struct_layout: (**s).clone(), - sized_fields, - last_unsized, + struct_type, field_name: s_field.field.name.clone(), field_index: i, actual_offset: s_field.rel_byte_offset, @@ -210,8 +219,8 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout s.clone(), + let element_ty = match u_ele.ty.get_layoutable_type().unwrap() { + LayoutableType::Sized(s) => s.clone(), _ => { unreachable!("elements of an array are always sized for TypeLayout") } @@ -254,34 +263,31 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout; - +/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. #[derive(thiserror::Error, Debug, Clone)] pub enum UniformLayoutError { #[error("{0}")] ArrayStride(WithContext), #[error("{0}")] - StructureFieldOffset(WithContext), - // TODO(chronicl) consider using CpuShareableType and implementing Display for it instead - // of converting to StoreType + StructureFieldOffset(WithContext), #[error( "The size of `{1}` on the gpu is not known at compile time. `{0}` \ requires that the size of uniform buffers on the gpu is known at compile time." )] - MustBeSized(&'static str, StoreType), + MustBeSized(&'static str, LayoutableType), } #[derive(Debug, Clone)] -pub enum Mismatch { +enum Mismatch { ArrayStride(ArrayStrideError), - StructureFieldOffset(StructureFieldOffsetError), + StructureFieldOffset(StructFieldOffsetError), } #[derive(Debug, Clone)] pub struct LayoutErrorContext { - s_layout: TypeLayout, - u_layout: TypeLayout, + s_layout: TypeLayout, + u_layout: TypeLayout, use_color: bool, } @@ -306,13 +312,13 @@ impl WithContext { pub struct ArrayStrideError { expected: u64, actual: u64, - element_ty: cs::SizedType, + element_ty: SizedType, } impl std::error::Error for WithContext {} impl Display for WithContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + let top_level: StoreType = self.ctx.s_layout.layoutable_type().clone().into(); writeln!( f, "array elements within type `{}` do not satisfy uniform layout requirements.", @@ -336,12 +342,11 @@ impl Display for WithContext { } } - +#[allow(missing_docs)] #[derive(Debug, Clone)] -pub struct StructureFieldOffsetError { +pub struct StructFieldOffsetError { pub struct_layout: StructLayout, - pub sized_fields: Vec, - pub last_unsized: Option, + pub struct_type: StructKind, pub field_name: CanonName, pub field_index: usize, pub actual_offset: u64, @@ -349,11 +354,16 @@ pub struct StructureFieldOffsetError { pub is_top_level: bool, } -impl std::error::Error for WithContext {} -impl Display for WithContext { +#[derive(Debug, Clone)] +pub enum StructKind { + Sized(SizedStruct), + Unsized(UnsizedStruct), +} + +impl std::error::Error for WithContext {} +impl Display for WithContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO(chronicl) remove conversion here if CpuShareableType implements Display - let top_level_type: StoreType = self.ctx.s_layout.cpu_shareable().clone().into(); + let top_level_type = self.ctx.s_layout.layoutable_type(); writeln!( f, "The type `{top_level_type}` cannot be used as a uniform buffer binding." @@ -372,8 +382,7 @@ impl Display for WithContext { write_struct_layout( &self.struct_layout, - &self.sized_fields, - self.last_unsized.as_ref(), + &self.struct_type, self.ctx.use_color, Some(self.field_index), f, @@ -391,18 +400,18 @@ impl Display for WithContext { set_color(f, None, false)?; writeln!(f)?; - writeln!(f, "Potential solutions include:"); + writeln!(f, "Potential solutions include:")?; writeln!( f, "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", expected_alignment, self.field_name - ); - writeln!(f, "- use a storage binding instead of a uniform binding"); + )?; + writeln!(f, "- use a storage binding instead of a uniform binding")?; writeln!( f, "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", self.field_name, expected_alignment - ); + )?; writeln!(f)?; writeln!( @@ -416,8 +425,7 @@ impl Display for WithContext { /// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. fn write_struct_layout( struct_layout: &StructLayout, - sized_fields: &[cs::SizedField], - last_unsized: Option<&cs::RuntimeSizedArrayField>, + struct_type: &StructKind, colored: bool, highlight_field: Option, f: &mut F, @@ -435,10 +443,15 @@ where false => Ok(()), }; + let (sized_fields, last_unsized) = match struct_type { + StructKind::Sized(s) => (s.fields(), None), + StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + let struct_name = &*struct_layout.name; let indent = " "; - let field_decl_line = |field: &cs::SizedField| { + let field_decl_line = |field: &SizedField| { let sized: ir::SizedType = field.ty.clone().into(); format!("{indent}{}: {},", field.name, sized) }; @@ -459,7 +472,7 @@ where if Some(i) == highlight_field { color(f, "#508EE3")?; } - let (align, size) = (layout.ty.byte_align(), layout.ty.byte_size().expect("is sized field")); + let (align, size) = (layout.ty.align(), layout.ty.byte_size().expect("is sized field")); let decl_line = field_decl_line(field); f.write_str(&decl_line)?; // write spaces to table on the right @@ -481,7 +494,7 @@ where for _ in decl_line.len()..table_start_column { f.write_char(' ')? } - write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.byte_align().as_u32())?; + write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.align().as_u32())?; } writeln!(f, "}}")?; Ok(()) diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index bc43f74..c194666 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -4,7 +4,7 @@ use super::*; #[derive(Clone)] pub struct LayoutMismatch { /// 2 (name, layout) pairs - layouts: [(String, TypeLayout); 2], + layouts: [(String, TypeLayout); 2], colored_error: bool, } @@ -65,7 +65,7 @@ impl LayoutMismatch { #[allow(clippy::needless_return)] pub(crate) fn write( indent: &str, - layouts: [(&str, &TypeLayout); 2], + layouts: [(&str, &TypeLayout); 2], colored: bool, f: &mut W, ) -> Result { @@ -270,19 +270,19 @@ impl LayoutMismatch { } write!(f, "{:width$}{indent}}}", ' ', width = pad_width); - let align_matches = a.byte_align() == b.byte_align(); + let align_matches = a.align() == b.align(); let size_matches = a.byte_size() == b.byte_size(); if !align_matches && size_matches { writeln!(f); color_a(f); - writeln!(f, "{a_name}{SEP}{indent}align={}", a.byte_align().as_u32()); + writeln!(f, "{a_name}{SEP}{indent}align={}", a.align().as_u32()); color_b(f); - writeln!(f, "{b_name}{SEP}{indent}align={}", b.byte_align().as_u32()); + writeln!(f, "{b_name}{SEP}{indent}align={}", b.align().as_u32()); color_reset(f); return Err(MismatchWasFound); } else { match align_matches { - true => write!(f, " align={}", a.byte_align().as_u32()), + true => write!(f, " align={}", a.align().as_u32()), false => write!(f, " align=?"), }; } @@ -431,7 +431,7 @@ impl LayoutMismatch { /// /// if the two layouts are not equal it uses the debug names in the returned /// error to tell the two layouts apart. -pub(crate) fn check_eq( +pub(crate) fn check_eq( a: (&str, &TypeLayout), b: (&str, &TypeLayout), ) -> Result<(), LayoutMismatch> diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs similarity index 81% rename from shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs rename to shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 9f075d4..76b62dd 100644 --- a/shame/src/frontend/rust_types/type_layout/cpu_shareable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -1,28 +1,16 @@ -use super::super::LayoutCalculator; +use super::super::{LayoutCalculator, Repr}; use super::*; -// Size and align of host-shareable types // +// Size and align of layoutable types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // -#[derive(Debug, Clone, Copy)] -pub enum Repr { - /// Wgsl storage address space layout / OpenGL std430 - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Storage, - /// Wgsl uniform address space layout / OpenGL std140 - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Uniform, - /// Packed layout. Vertex buffer only. - Packed, -} - impl SizedType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, repr: Repr) -> u64 { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(Major::Row), + SizedType::Matrix(m) => m.byte_size(MatrixMajor::Row), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -34,7 +22,7 @@ impl SizedType { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(), - SizedType::Matrix(m) => m.align(repr, Major::Row), + SizedType::Matrix(m) => m.align(repr, MatrixMajor::Row), SizedType::Atomic(a) => a.align(), SizedType::PackedVec(v) => v.align(), SizedType::Struct(s) => s.byte_size_and_align(repr).1, @@ -52,6 +40,10 @@ impl SizedType { impl SizedStruct { + /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the + /// fields of this struct, as well as a `byte_size` and `align` calculator. + /// See the documentation of `FieldOffsets` on how to obtain `byte_size` and `align` + /// of this struct from it. pub fn field_offsets(&self, repr: Repr) -> FieldOffsets { FieldOffsets { fields: &self.fields, @@ -74,10 +66,13 @@ impl SizedStruct { } } -/// An iterator over the field offsets of a `SizedStruct`. +/// An iterator over the field offsets of a `SizedStruct` or the sized fields of an `UnsizedStruct`. /// /// `FieldOffsets::byte_size` and `FieldOffsets::byte_align` can be used to query the struct's /// `byte_size` and `byte_align`, but only takes into account the fields that have been iterated over. +/// +/// Use [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset +/// and the struct's align for `UnsizedStruct`s. pub struct FieldOffsets<'a> { fields: &'a [SizedField], field_index: usize, @@ -125,6 +120,10 @@ impl FieldOffsets<'_> { impl UnsizedStruct { + /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the + /// sized fields of this struct. `FieldOffsets` may be also passed to + /// [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset + /// and the struct's align. pub fn sized_field_offsets(&self, repr: Repr) -> FieldOffsets { FieldOffsets { fields: &self.sized_fields, @@ -164,6 +163,7 @@ const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { } } +#[allow(missing_docs)] impl Vector { pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } @@ -180,6 +180,7 @@ impl Vector { } } +#[allow(missing_docs)] impl ScalarType { pub const fn byte_size(&self) -> u64 { match self { @@ -198,28 +199,30 @@ impl ScalarType { } } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy)] -pub enum Major { +pub enum MatrixMajor { Row, Column, } +#[allow(missing_docs)] impl Matrix { - pub const fn byte_size(&self, major: Major) -> u64 { + pub const fn byte_size(&self, major: MatrixMajor) -> u64 { let (vec, array_len) = self.as_vector_array(major); let array_stride = array_stride(vec.align(), vec.byte_size()); array_size(array_stride, array_len) } - pub const fn align(&self, repr: Repr, major: Major) -> U32PowerOf2 { + pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { let (vec, _) = self.as_vector_array(major); array_align(vec.align(), repr) } - const fn as_vector_array(&self, major: Major) -> (Vector, NonZeroU32) { + const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { let (vec_len, array_len): (Len, NonZeroU32) = match major { - Major::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), - Major::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), + MatrixMajor::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), + MatrixMajor::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), }; ( Vector { @@ -231,11 +234,13 @@ impl Matrix { } } +#[allow(missing_docs)] impl Atomic { pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } pub const fn align(&self) -> U32PowerOf2 { self.scalar.as_scalar_type().align() } } +#[allow(missing_docs)] impl SizedArray { pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } @@ -247,33 +252,41 @@ impl SizedArray { } } +/// Returns an array's size given it's stride and length. +/// +/// Note, this is independent of layout rules (`Repr`). pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } -pub const fn array_align(element_align: U32PowerOf2, layout: Repr) -> U32PowerOf2 { - match layout { +/// Returns an array's size given the alignment of it's elements. +pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage | Repr::Packed => element_align, Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), } } +/// Returns an array's size given the alignment and size of it's elements. pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { // Arrays of element type T must have an element stride that is a multiple of the // RequiredAlignOf(T, C) for the address space C: round_up(element_align.as_u64(), element_size) } +#[allow(missing_docs)] impl RuntimeSizedArray { pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.byte_align(repr), self.element.byte_size(repr)) } } +#[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.byte_align(repr) } } +#[allow(missing_docs)] impl RuntimeSizedArrayField { pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.byte_align(repr) } } diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs similarity index 70% rename from shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs rename to shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index c60e3fc..17b67ec 100644 --- a/shame/src/frontend/rust_types/type_layout/cpu_shareable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -1,9 +1,15 @@ use super::*; -impl LayoutType { +impl LayoutableType { + /// Fallibly creates a new `LayoutableType` of a struct. + /// + /// An error is returned if the following rules aren't followed: + /// - There must be at least one field. + /// - None of the fields must be an `UnsizedStruct`. + /// - Only the last field may be unsized (a runtime sized array). pub fn struct_from_parts( struct_name: impl Into, - fields: impl IntoIterator, + fields: impl IntoIterator, ) -> Result { use StructFromPartsError::*; @@ -16,13 +22,13 @@ impl LayoutType { .into_iter() .map(|(options, ty)| { Ok(match ty { - LayoutType::Sized(s) => Field::Sized(SizedField::new(options, s)), - LayoutType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( + LayoutableType::Sized(s) => Field::Sized(SizedField::new(options, s)), + LayoutableType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( options.name, options.custom_min_align, a.element, )), - LayoutType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), + LayoutableType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), }) }) .peekable(); @@ -60,6 +66,7 @@ impl LayoutType { } } +#[allow(missing_docs)] #[derive(thiserror::Error, Debug)] pub enum StructFromPartsError { #[error("Struct must have at least one field.")] @@ -81,13 +88,6 @@ impl SizedStruct { } } - pub fn from_parts(name: impl Into, fields: Vec) -> Self { - Self { - name: name.into(), - fields, - } - } - /// Adds a sized field to the struct. pub fn extend(mut self, field_options: impl Into, ty: impl Into) -> Self { self.fields.push(SizedField::new(field_options, ty)); @@ -111,9 +111,9 @@ impl SizedStruct { /// Adds either a `SizedType` or a `RuntimeSizedArray` field to the struct. /// - /// Returns a `HostshareableType`, because the `Self` may either stay + /// Returns a `LayoutableType`, because the `Self` may either stay /// a `SizedStruct` or become an `UnsizedStruct` depending on the field's type. - pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutType { + pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutableType { let options = field_options.into(); match field { SizedOrArray::Sized(ty) => self.extend(options, ty).into(), @@ -123,24 +123,37 @@ impl SizedStruct { } } - pub fn new_sized_or_array( - struct_name: impl Into, - field_options: impl Into, - field: SizedOrArray, - ) -> LayoutType { - let options = field_options.into(); - match field { - SizedOrArray::Sized(ty) => Self::new(struct_name, options, ty).into(), - SizedOrArray::RuntimeSizedArray(a) => UnsizedStruct { - name: struct_name.into(), - sized_fields: Vec::new(), - last_unsized: RuntimeSizedArrayField::new(options.name, options.custom_min_align, a.element), - } - .into(), + /// The fields of this struct. + pub fn fields(&self) -> &[SizedField] { &self.fields } + + pub(crate) fn from_parts(name: impl Into, fields: Vec) -> Self { + Self { + name: name.into(), + fields, } } +} - pub fn fields(&self) -> &[SizedField] { &self.fields } +#[allow(missing_docs)] +pub enum SizedOrArray { + Sized(SizedType), + RuntimeSizedArray(RuntimeSizedArray), +} + +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +#[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] +pub struct IsUnsizedStructError; +impl TryFrom for SizedOrArray { + type Error = IsUnsizedStructError; + + fn try_from(value: LayoutableType) -> Result { + match value { + LayoutableType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), + LayoutableType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), + LayoutableType::UnsizedStruct(_) => Err(IsUnsizedStructError), + } + } } impl> From<(T, SizedType)> for SizedField { diff --git a/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs similarity index 64% rename from shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs rename to shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 42f6a5e..4a28875 100644 --- a/shame/src/frontend/rust_types/type_layout/cpu_shareable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -1,37 +1,38 @@ -#![allow(missing_docs)] +//! This module defines types that can be laid out in memory. + use std::{num::NonZeroU32, rc::Rc}; use crate::{ any::U32PowerOf2, ir::{self, ir_type::BufferBlockDefinitionError, StructureFieldNamesMustBeUnique}, - GpuAligned, GpuSized, GpuStore, GpuType, NoBools, NoHandles, }; -// TODO(chronicl) -// - Consider moving this module into ir_type? -// - We borrow these types from `StoreType` currently. Maybe it would be better the other -// way around - `StoreType` should borrow from `HostShareableType`. -pub use crate::ir::{Len, Len2, LenEven, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{constraint, FieldOptions}; +pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; +use super::FieldOptions; -mod align_size; -mod builder; +pub(crate) mod align_size; +pub(crate) mod builder; -pub use align_size::*; -pub use builder::*; +pub use align_size::{FieldOffsets, MatrixMajor, array_size, array_stride, array_align}; +pub use builder::SizedOrArray; -// TODO(chronicl) rewrite -/// This reprsents a wgsl spec compliant host-shareable type with the addition -/// that f64 is a supported scalar type. +/// Types that have a defined memory layout. /// -/// https://www.w3.org/TR/WGSL/#host-shareable-types +/// `LayoutableType` does not contain any layout information itself, but a layout +/// can be assigned to it using [`TypeLayout`] according to one of the available layout rules: +/// storage, uniform or packed. #[derive(Debug, Clone)] -pub enum LayoutType { +pub enum LayoutableType { + /// A type with a statically known size. Sized(SizedType), + /// A struct with a runtime sized array as it's last field. UnsizedStruct(UnsizedStruct), + /// An array whose size is determined at runtime. RuntimeSizedArray(RuntimeSizedArray), } +/// Types with a statically known size. +#[allow(missing_docs)] #[derive(Debug, Clone)] pub enum SizedType { Vector(Vector), @@ -42,12 +43,14 @@ pub enum SizedType { Struct(SizedStruct), } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Vector { pub scalar: ScalarType, pub len: Len, } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Matrix { pub scalar: ScalarTypeFp, @@ -55,23 +58,30 @@ pub struct Matrix { pub rows: Len2, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct SizedArray { pub element: Rc, pub len: NonZeroU32, } +#[allow(missing_docs)] #[derive(Debug, Clone, Copy)] pub struct Atomic { pub scalar: ScalarTypeInteger, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct RuntimeSizedArray { pub element: SizedType, } -/// Same as `ir::ScalarType`, but without `ScalarType::Bool`. +/// Scalar types with known memory layout. +/// +/// Same as `ir::ScalarType`, but without `ScalarType::Bool` since booleans +/// don't have a standardized memory representation. +#[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ScalarType { F16, @@ -81,20 +91,29 @@ pub enum ScalarType { F64, } +/// A struct with a known fixed size. #[derive(Debug, Clone)] pub struct SizedStruct { + /// The canonical name of the struct. pub name: CanonName, // This is private to ensure a `SizedStruct` always has at least one field. fields: Vec, } +/// A struct whose size is not known at compile time. +/// +/// This struct has a runtime sized array as it's last field. #[derive(Debug, Clone)] pub struct UnsizedStruct { + /// The canonical name of the struct. pub name: CanonName, + /// Fixed-size fields that come before the unsized field pub sized_fields: Vec, + /// Last runtime sized array field of the struct. pub last_unsized: RuntimeSizedArrayField, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct SizedField { pub name: CanonName, @@ -103,6 +122,7 @@ pub struct SizedField { pub ty: SizedType, } +#[allow(missing_docs)] #[derive(Debug, Clone)] pub struct RuntimeSizedArrayField { pub name: CanonName, @@ -111,6 +131,7 @@ pub struct RuntimeSizedArrayField { } impl SizedField { + /// Creates a new `SizedField`. pub fn new(options: impl Into, ty: impl Into) -> Self { let options = options.into(); Self { @@ -123,6 +144,8 @@ impl SizedField { } impl RuntimeSizedArrayField { + /// Creates a new `RuntimeSizedArrayField` given it's field name, + /// an optional custom minimum align and it's element type. pub fn new( name: impl Into, custom_min_align: Option, @@ -139,6 +162,7 @@ impl RuntimeSizedArrayField { } impl SizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type and length. pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { Self { element: Rc::new(element_ty.into()), @@ -148,6 +172,7 @@ impl SizedArray { } impl RuntimeSizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type. pub fn new(element_ty: impl Into) -> Self { RuntimeSizedArray { element: element_ty.into(), @@ -155,32 +180,15 @@ impl RuntimeSizedArray { } } -pub enum SizedOrArray { - Sized(SizedType), - RuntimeSizedArray(RuntimeSizedArray), -} - -#[derive(thiserror::Error, Debug)] -#[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] -pub struct IsUnsizedStruct; -impl TryFrom for SizedOrArray { - type Error = IsUnsizedStruct; - - fn try_from(value: LayoutType) -> Result { - match value { - LayoutType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), - LayoutType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), - LayoutType::UnsizedStruct(_) => Err(IsUnsizedStruct), - } - } -} - -// TODO(chronicl) documentation -pub trait BinaryRepr { - fn layout_type() -> LayoutType; +/// Trait for types that have a well-defined memory layout. +pub trait Layoutable { + /// Returns the `LayoutableType` representation for this type. + fn layoutable_type() -> LayoutableType; } -pub trait BinaryReprSized: BinaryRepr { - fn layout_type_sized() -> SizedType; +/// Trait for types that have a well-defined memory layout and statically known size. +pub trait LayoutableSized: Layoutable { + /// Returns the `SizedType` representation for this type. + fn layoutable_type_sized() -> SizedType; } @@ -196,7 +204,7 @@ impl std::fmt::Display for ScalarType { } } -// Conversions to ScalarType, SizedType and HostshareableType // +// Conversions to ScalarType, SizedType and LayoutableType // macro_rules! impl_into_sized_type { ($($ty:ident -> $variant:path),*) => { @@ -204,9 +212,9 @@ macro_rules! impl_into_sized_type { impl $ty { /// Const conversion to [`SizedType`] pub const fn into_sized_type(self) -> SizedType { $variant(self) } - /// Const conversion to [`HostshareableType`] - pub const fn into_cpu_shareable(self) -> LayoutType { - LayoutType::Sized(self.into_sized_type()) + /// Const conversion to [`LayoutableType`] + pub const fn into_layoutable_type(self) -> LayoutableType { + LayoutableType::Sized(self.into_sized_type()) } } @@ -225,18 +233,18 @@ impl_into_sized_type!( PackedVector -> SizedType::PackedVec ); -impl From for LayoutType +impl From for LayoutableType where SizedType: From, { - fn from(value: T) -> Self { LayoutType::Sized(SizedType::from(value)) } + fn from(value: T) -> Self { LayoutableType::Sized(SizedType::from(value)) } } -impl From for LayoutType { - fn from(s: UnsizedStruct) -> Self { LayoutType::UnsizedStruct(s) } +impl From for LayoutableType { + fn from(s: UnsizedStruct) -> Self { LayoutableType::UnsizedStruct(s) } } -impl From for LayoutType { - fn from(a: RuntimeSizedArray) -> Self { LayoutType::RuntimeSizedArray(a) } +impl From for LayoutableType { + fn from(a: RuntimeSizedArray) -> Self { LayoutableType::RuntimeSizedArray(a) } } impl ScalarTypeInteger { @@ -266,12 +274,12 @@ impl From for ScalarType { // Conversions to ir types // -impl From for ir::StoreType { - fn from(host: LayoutType) -> Self { +impl From for ir::StoreType { + fn from(host: LayoutableType) -> Self { match host { - LayoutType::Sized(s) => ir::StoreType::Sized(s.into()), - LayoutType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), - LayoutType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), + LayoutableType::Sized(s) => ir::StoreType::Sized(s.into()), + LayoutableType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), + LayoutableType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), } } } @@ -359,12 +367,13 @@ impl From for ir::ir_type::RuntimeSizedArrayField { // Conversions from ir types // +/// Type contains bools, which doesn't have a known layout. #[derive(thiserror::Error, Debug)] -#[error("Type contains bools, which isn't cpu shareable.")] -pub struct ContainsBools; +#[error("Type contains bools, which doesn't have a known layout.")] +pub struct ContainsBoolsError; impl TryFrom for ScalarType { - type Error = ContainsBools; + type Error = ContainsBoolsError; fn try_from(value: ir::ScalarType) -> Result { Ok(match value { @@ -373,18 +382,13 @@ impl TryFrom for ScalarType { ir::ScalarType::F64 => ScalarType::F64, ir::ScalarType::U32 => ScalarType::U32, ir::ScalarType::I32 => ScalarType::I32, - ir::ScalarType::Bool => return Err(ContainsBools), + ir::ScalarType::Bool => return Err(ContainsBoolsError), }) } } -impl ir::ScalarType { - // TODO(chronicl) remove - pub fn as_host_shareable_unchecked(self) -> ScalarType { self.try_into().unwrap() } -} - impl TryFrom for SizedType { - type Error = ContainsBools; + type Error = ContainsBoolsError; fn try_from(value: ir::SizedType) -> Result { Ok(match value { @@ -404,7 +408,7 @@ impl TryFrom for SizedType { } impl TryFrom for SizedStruct { - type Error = ContainsBools; + type Error = ContainsBoolsError; fn try_from(structure: ir::ir_type::SizedStruct) -> Result { let mut fields = Vec::new(); @@ -425,35 +429,38 @@ impl TryFrom for SizedStruct { } } +/// Errors that can occur when converting IR types to layoutable types. #[derive(thiserror::Error, Debug)] -pub enum CpuShareableConversionError { - #[error("Type contains bools, which isn't cpu shareable.")] +pub enum LayoutableConversionError { + /// Type contains bools, which don't have a standardized memory layout. + #[error("Type contains bools, which don't have a standardized memory layout.")] ContainsBool, - #[error("Type is a handle, which isn't cpu shareable.")] + /// Type is a handle, which don't have a standardized memory layout. + #[error("Type is a handle, which don't have a standardized memory layout.")] IsHandle, } -impl From for CpuShareableConversionError { - fn from(_: ContainsBools) -> Self { Self::ContainsBool } +impl From for LayoutableConversionError { + fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } } -impl TryFrom for LayoutType { - type Error = CpuShareableConversionError; +impl TryFrom for LayoutableType { + type Error = LayoutableConversionError; fn try_from(value: ir::StoreType) -> Result { Ok(match value { - ir::StoreType::Sized(sized_type) => LayoutType::Sized(sized_type.try_into()?), - ir::StoreType::RuntimeSizedArray(element) => LayoutType::RuntimeSizedArray(RuntimeSizedArray { + ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { element: element.try_into()?, }), ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, - ir::StoreType::Handle(_) => return Err(CpuShareableConversionError::IsHandle), + ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), }) } } -impl TryFrom for LayoutType { - type Error = ContainsBools; +impl TryFrom for LayoutableType { + type Error = ContainsBoolsError; fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { let mut sized_fields = Vec::new(); @@ -491,3 +498,37 @@ impl TryFrom for LayoutType { .into()) } } + +// Display impls + +impl std::fmt::Display for LayoutableType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LayoutableType::Sized(s) => write!(f, "{s}"), + LayoutableType::RuntimeSizedArray(a) => write!(f, "Array<{}>", a.element), + LayoutableType::UnsizedStruct(s) => write!(f, "{}", s.name), + } + } +} + +impl std::fmt::Display for SizedType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SizedType::Vector(v) => write!(f, "{}{}", ir::ScalarType::from(v.scalar), v.len), + SizedType::Matrix(m) => { + write!( + f, + "mat<{}, {}, {}>", + ir::ScalarType::from(m.scalar), + Len::from(m.columns), + Len::from(m.rows) + ) + } + SizedType::Array(a) => write!(f, "Array<{}, {}>", &*a.element, a.len), + SizedType::Atomic(a) => write!(f, "Atomic<{}>", ir::ScalarType::from(a.scalar)), + // TODO(chronicl) figure out scalar type display + SizedType::PackedVec(p) => write!(f, "PackedVec<{:?}, {}>", p.scalar_type, Len::from(p.len)), + SizedType::Struct(s) => write!(f, "{}", s.name), + } + } +} diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 8c49ad0..9acbe90 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -1,8 +1,5 @@ -#![allow(missing_docs)] // TODO(chronicl) remove //! Everything related to type layouts. - - use std::{ fmt::{Debug, Display, Write}, hash::Hash, @@ -16,25 +13,20 @@ use crate::{ common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, ir::{ self, - ir_type::{round_up, CanonName, ScalarTypeFp}, + ir_type::{round_up, CanonName}, recording::Context, - Len, SizedType, Type, + Len, }, }; -use cpu_shareable::{LayoutType, Matrix, Vector}; -use thiserror::Error; +use layoutable::{LayoutableType, Matrix, Vector}; -mod builder; -pub mod cpu_shareable; -mod eq; - -pub use builder::*; -pub(crate) use eq::*; +pub(crate) mod builder; +pub(crate) mod eq; +pub(crate) mod layoutable; /// The type contained in the bytes of a `TypeLayout`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TypeLayoutSemantics { - // TODO(chronicl) consider replacing this scalar type with the host shareable version. /// `vec` Vector(Vector), /// special compressed vectors for vertex attribute types @@ -54,45 +46,37 @@ pub enum TypeLayoutSemantics { /// This models only the layout, not other characteristics of the types. /// For example an `Atomic>` is treated like a regular `vec` layout wise. /// -/// ### Constraint generic +/// ### Generic +/// +/// `TypeLayout` has a generic `T: TypeRepr`, which is used to statically guarantee that +/// it follows specific layout rules. +/// +/// The following types implementing `TypeRepr` exist and can be found in [`shame::any::repr`]: /// -/// `TypeLayout` has a generic `T: TypeConstraint`, which guarantees that the layout -/// follows certain layout rules. The following layout rules exist and are captured by -/// the [`Repr`] enum: /// ``` -/// pub enum Repr { -/// Storage, /// wgsl storage address space layout / OpenGL std430 -/// Uniform, /// wgsl uniform address space layout / OpenGL std140 -/// Packed, /// packed layout, vertex buffer only -/// } +/// struct Plain; /// May not follow any layout rules. This is the default. +/// struct Storage; /// wgsl storage address space layout / OpenGL std430 +/// struct Uniform; /// wgsl uniform address space layout / OpenGL std140 +/// struct Packed; /// Packed layout /// ``` /// /// More information on the exact details of these layout rules is available here /// /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints /// -/// The following types implementing `TypeConstraint` exist and can be found in [`constraint`]: -/// +/// The following methods exist for creating new type layouts based on a [`LayoutableType`] /// ``` -/// struct Plain; /// May not follow any layout rules. -/// struct Storage; /// Follows `Repr::Storage` layout rules. -/// struct Uniform; /// Follows `Repr::Uniform` layout rules. -/// struct Vertex; /// Follows either `Repr::Packed` or both -/// /// `Repr::Storage` and `Repr::Uniform`. +/// let layout_type: LayoutableType = f32x1::layoutable_type(); +/// let _ = TypeLayout::new_layout_for(layout_type); +/// let _ = TypeLayout::new_storage_layout_for(layout_type); +/// let _ = TypeLayout::new_uniform_layout_for(layout_type); +/// let _ = TypeLayout::new_packed_layout_for(layout_type); /// ``` -/// `Vertex` also guarantees that it is either the layout of a [`VertexAttribute`] or the layout -/// of a struct with fields that are all `VertexAttribute`s, which is why `Repr::Storage` -/// and `Repr::Uniform` are equivalent for it. -/// -/// `Storage` and `Uniform` are always based on a [`CpuShareableType`], which can be accessed -/// using [`TypeLayout::cpu_shareable`]. They also guarantee, that all nested `TypeLayout`s -/// (for example struct fields) are also based on a `CpuShareableType`, which can only be accessed -/// through `TypeLayout::get_cpu_shareable`. /// -/// TODO(chronicl) make sure these are caught: -/// There is some target language specific constraints that `TypeLayout` does not capture: -/// - `TypeLayout` may be unsized, which is not allowed for wgsl. -/// - `custom_min_align` and `custom_min_size` are not supported for glsl. +/// Non-`Plain` layouts are always based on a [`LayoutableType`], which can be accessed +/// using [`TypeLayout::layoutable_type`]. They also guarantee, that all nested `TypeLayout`s +/// (for example struct fields) are also based on a `LayoutableType`, which can only be accessed +/// through `TypeLayout::get_layoutable_type`. /// /// ### Layout comparison /// @@ -100,18 +84,17 @@ pub enum TypeLayoutSemantics { /// "do these two types have the same layout" so that uploading a type to the gpu /// will result in no memory errors. /// -/// A layout comparison looks like this: +/// a layout comparison looks like this: /// ``` -/// assert!(f32::cpu_layout() == vec::gpu_layout().into_unconstraint()); +/// assert!(f32::cpu_layout() == vec::gpu_layout()); /// // or, more explicitly /// assert_eq!( /// ::cpu_layout(), /// as GpuLayout>::gpu_layout(), /// ); /// ``` -/// #[derive(Clone)] -pub struct TypeLayout { +pub struct TypeLayout { /// size in bytes (Some), or unsized (None) pub byte_size: Option, /// the byte alignment @@ -122,46 +105,57 @@ pub struct TypeLayout { pub kind: TypeLayoutSemantics, /// Is some for `constraint::Storage` and `constraint::Uniform`. Can be converted to a `StoreType`. - pub(crate) cpu_shareable: Option, + pub(crate) layoutable_type: Option, _phantom: PhantomData, } // PartialEq, Eq, Hash for TypeLayout -impl PartialEq> for TypeLayout { +impl PartialEq> for TypeLayout { fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } } -impl Eq for TypeLayout {} -impl Hash for TypeLayout { +impl Eq for TypeLayout {} +impl Hash for TypeLayout { fn hash(&self, state: &mut H) { self.byte_size.hash(state); self.kind.hash(state); } } -use constraint::TypeConstraint; +pub use repr::{TypeRepr, Repr}; /// Module for all restrictions on `TypeLayout`. -pub mod constraint { +pub mod repr { use super::*; - /// Type restriction of a `TypeLayout`. This allows type layouts - /// to have guarantees based on their type restriction. For example a - /// `TypeLayout can be used in vertex buffers. For more - /// details see the documentation of [`TypeLayout`]. - pub trait TypeConstraint: Clone + PartialEq + Eq {} + /// Type representation used by `TypeLayout`. This provides guarantees + /// about the alignment rules that the type layout adheres to. + /// See [`TypeLayout`] documentation for more details. + pub trait TypeRepr: Clone + PartialEq + Eq {} + + /// Enum of layout rules. + #[derive(Debug, Clone, Copy)] + pub enum Repr { + /// Wgsl storage address space layout / OpenGL std430 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Storage, + /// Wgsl uniform address space layout / OpenGL std140 + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Uniform, + /// Packed layout. Vertex buffer only. + Packed, + } - macro_rules! type_restriction { + macro_rules! type_repr { ($($constraint:ident),*) => { $( - /// A type restriction for `TypeLayout`. - /// See [`TypeRestriction`] documentation for TODO(chronicl) + /// A type representation used by `TypeLayout`. + /// See [`TypeLayout`] documentation for more details. #[derive(Clone, PartialEq, Eq, Hash)] pub struct $constraint; - impl TypeConstraint for $constraint {} + impl TypeRepr for $constraint {} )* }; } - - type_restriction!(Plain, Storage, Uniform, Packed); + type_repr!(Plain, Storage, Uniform, Packed); macro_rules! impl_from_into { ($from:ident -> $($into:ident),*) => { @@ -183,13 +177,13 @@ impl TypeLayout { byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics, - hostshareable: Option, + layoutable_type: Option, ) -> Self { TypeLayout { byte_size, align: byte_align, kind, - cpu_shareable: hostshareable, + layoutable_type, _phantom: PhantomData, } } @@ -201,22 +195,22 @@ impl TypeLayout { pub(in super::super::rust_types) mod type_layout_internal { use super::*; - pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { + pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { TypeLayout { byte_size: layout.byte_size, align: layout.align, kind: layout.kind, - cpu_shareable: layout.cpu_shareable, + layoutable_type: layout.layoutable_type, _phantom: PhantomData, } } } -/// `LayoutCalculator` helps calculate the size, align and the field offsets of a gpu struct. +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. /// /// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s -/// are ignored and the field is directly after the previous field. However, -/// a `custom_min_align` that is `Some` overwrites the `packedness` of the field. +/// are ignored and the field is inserted directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. #[derive(Debug, Clone)] pub struct LayoutCalculator { next_offset_min: u64, @@ -321,10 +315,11 @@ impl LayoutCalculator { } /// a sized or unsized struct type with 0 or more fields -#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StructLayout { + /// The canonical name of the structure type, ignored in equality/hash comparisons pub name: IgnoreInEqOrdHash, + /// The fields of the structure with their memory offsets pub fields: Vec, } @@ -338,8 +333,10 @@ impl StructLayout { #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FieldLayoutWithOffset { + /// The layout information for the field pub field: FieldLayout, - pub rel_byte_offset: u64, // this being relative is used in TypeLayout::byte_size + /// The relative byte offset of this field from the start of its containing structure + pub rel_byte_offset: u64, } impl std::ops::Deref for FieldLayoutWithOffset { @@ -352,56 +349,32 @@ impl std::ops::Deref for FieldLayoutWithOffset { pub struct ElementLayout { /// Stride of the elements pub byte_stride: u64, - /// The elment layout must be constraint::Plain, because it's shared by all constraints. - // `ElementLayout` could possibly be made generic too, but it would complicate a lot. - pub ty: TypeLayout, -} - -/// the layout rules used when calculating the byte offsets and alignment of a type -#[derive(Debug, Clone, Copy)] -pub enum TypeLayoutRules { - /// wgsl type layout rules, see https://www.w3.org/TR/WGSL/#memory-layouts - Wgsl, - // reprC, - // Std140, - // Std430, - // Scalar, -} - -#[allow(missing_docs)] -#[derive(Error, Debug, Clone)] -pub enum TypeLayoutError { - #[error("An array cannot contain elements of an unsized type {elements}")] - ArrayOfUnsizedElements { elements: TypeLayout }, - #[error("the type `{0}` has no defined {1:?} layout in shaders")] - LayoutUndefined(Type, TypeLayoutRules), - #[error("in `{parent_name}` at field `{field_name}`: {error}")] - AtField { - parent_name: CanonName, - field_name: CanonName, - error: Rc, - }, - #[error("in element of array: {0}")] - InArrayElement(Rc), + /// The type layout of each element in the array. + /// + /// The element layout must be constraint::Plain because it's shared by all constraints. + /// `ElementLayout` could possibly be made generic too, but it would complicate a lot. + pub ty: TypeLayout, } -impl TypeLayout { - /// Byte size of the represented type. +impl TypeLayout { + /// Returns the byte size of the represented type. + /// + /// For sized types, this returns Some(size), while for unsized types + /// (like runtime-sized arrays), this returns None. pub fn byte_size(&self) -> Option { self.byte_size } - /// Align of the represented type. - pub fn byte_align(&self) -> U32PowerOf2 { self.align } + /// Returns the alignment requirement of the represented type. + pub fn align(&self) -> U32PowerOf2 { self.align } /// Although all TypeLayout always implement Into, this method /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. - pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } + pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } - /// Get the `CpuShareableType` this layout is based on. + /// Get the `LayoutableType` this layout is based on. /// - /// This may be `None` for type layouts - /// that aren't `TypeLayout` or `TypeLayout`. Those two also offer direct - /// access via [`TypeLayout::cpu_shareable`]. - pub fn get_cpu_shareable(&self) -> Option<&LayoutType> { self.cpu_shareable.as_ref() } + /// This may be `None` for `TypeLayout`. + /// `TypeLayout` may use [`TypeLayout::layoutable_type`]. + pub fn get_layoutable_type(&self) -> Option<&LayoutableType> { self.layoutable_type.as_ref() } /// a short name for this `TypeLayout`, useful for printing inline pub fn short_name(&self) -> String { @@ -502,8 +475,8 @@ impl TypeLayout { } // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings - pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { - let t: cpu_shareable::LayoutType = store_type.try_into()?; + pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + let t: layoutable::LayoutableType = store_type.try_into()?; Ok(TypeLayout::new_storage_layout_for(t).into()) } } @@ -517,24 +490,10 @@ pub struct FieldLayout { pub custom_min_align: IgnoreInEqOrdHash>, /// The fields layout must be constraint::Plain, /// because that's the most it can be while supporting all possible constraints. - pub ty: TypeLayout, + pub ty: TypeLayout, } impl FieldLayout { - pub fn new( - name: CanonName, - custom_min_size: Option, - custom_min_align: Option, - ty: TypeLayout, - ) -> Self { - Self { - name, - custom_min_size: custom_min_size.into(), - custom_min_align: custom_min_align.into(), - ty, - } - } - fn byte_size(&self) -> Option { self.ty .byte_size() @@ -542,7 +501,7 @@ impl FieldLayout { } /// The alignment of the field with `custom_min_align` taken into account. - fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.byte_align(), self.custom_min_align.0) } + fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.align(), self.custom_min_align.0) } const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { // const byte_size.max(custom_min_size.unwrap_or(0)) @@ -555,7 +514,7 @@ impl FieldLayout { } const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1) as u32 as u64) + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) if let Some(min_align) = custom_min_align { align.max(min_align) } else { @@ -602,13 +561,13 @@ impl> From for FieldOptions { fn from(name: T) -> Self { Self::new(name, None, None) } } -impl Display for TypeLayout { +impl Display for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); self.write("", colored, f) } } -impl Debug for TypeLayout { +impl Debug for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } } diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 78ff297..4ba3e2d 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -8,7 +8,6 @@ use super::{ }; use crate::{ frontend::any::shared_io::{BindPath, BindingType}, - cpu_shareable::{LayoutType}, TypeLayout, }; use crate::{ diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index b25587e..df5cf8f 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,20 +7,16 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, - type_layout::{ - cpu_shareable::{self, BinaryReprSized}, - TypeLayoutRules, - }, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; use crate::{ + any::layout::{self, Layoutable, LayoutableSized}, call_info, common::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, small_vec::SmallVec, }, - cpu_shareable::BinaryRepr, frontend::encoding::{ buffer::{BufferAddressSpace, BufferInner, BufferRefInner}, rasterizer::Gradient, @@ -577,26 +573,26 @@ impl GpuStore for vec { } -impl BinaryReprSized for vec +impl LayoutableSized for vec where vec: NoBools, { - fn layout_type_sized() -> cpu_shareable::SizedType { - cpu_shareable::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() + fn layoutable_type_sized() -> layout::SizedType { + layout::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() } } -impl BinaryRepr for vec +impl Layoutable for vec where vec: NoBools, { - fn layout_type() -> cpu_shareable::LayoutType { Self::layout_type_sized().into() } + fn layoutable_type() -> layout::LayoutableType { Self::layoutable_type_sized().into() } } impl GpuLayout for vec where vec: NoBools, { - fn gpu_repr() -> cpu_shareable::Repr { cpu_shareable::Repr::Storage } + fn gpu_repr() -> layout::Repr { layout::Repr::Storage } fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> @@ -1119,7 +1115,7 @@ where Self: NoBools, { fn vertex_attrib_format() -> VertexAttribFormat { - VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.as_host_shareable_unchecked()) + VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.try_into().expect("no bools vec")) } } diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index 5af9ed9..f2e2182 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -6,14 +6,12 @@ use std::{ use thiserror::Error; -use crate::{common::proc_macro_reexports::TypeLayoutRules, frontend::rust_types::type_layout::TypeLayout}; +use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, TypeLayout}; use crate::{ backend::language::Language, call_info, common::prettify::set_color, - frontend::{ - any::shared_io::BufferBindingType, encoding::EncodingErrorKind, rust_types::type_layout::LayoutMismatch, - }, + frontend::{any::shared_io::BufferBindingType, encoding::EncodingErrorKind}, ir::{ ir_type::{max_u64_po2_dividing, AccessModeReadable}, recording::{Context, MemoryRegion}, diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 64e7282..e5ded39 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -479,6 +479,7 @@ pub struct PackedVector { } /// exhaustive list of all byte sizes a `packed_vec` can have +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PackedVectorByteSize { _2, _4, diff --git a/shame/src/ir/ir_type/ty.rs b/shame/src/ir/ir_type/ty.rs index ead22d6..5647974 100644 --- a/shame/src/ir/ir_type/ty.rs +++ b/shame/src/ir/ir_type/ty.rs @@ -1,6 +1,6 @@ use self::struct_::SizedStruct; use crate::{ - frontend::{any::shared_io, rust_types::type_layout::TypeLayoutRules}, + frontend::{any::shared_io}, ir::recording::MemoryRegion, }; diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 57e21fd..10bf448 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -16,7 +16,6 @@ use crate::{ po2::U32PowerOf2, pool::{Key, PoolRef}, }, - cpu_shareable, frontend::{ any::{ render_io::{Attrib, ColorTarget, Location, VertexBufferLayout}, @@ -33,7 +32,7 @@ use crate::{ error::InternalError, rust_types::{ len::x3, - type_layout::{StructLayout, TypeLayoutRules}, + type_layout::{self, layoutable, StructLayout}, }, }, ir::{ @@ -44,7 +43,6 @@ use crate::{ StructureFieldNamesMustBeUnique, TextureFormatWrapper, Type, }, results::DepthStencilState, - type_layout::TypeLayoutSemantics, BindingIter, DepthLhs, StencilMasking, Test, TypeLayout, }; @@ -354,12 +352,12 @@ impl WipPushConstantsField { let byte_size = sized_struct.byte_size(); // TODO(release) the `.expect()` calls here can be removed by building a `std::alloc::Layout`-like builder for struct layouts. - let sized_struct: cpu_shareable::SizedStruct = sized_struct + let sized_struct: layoutable::SizedStruct = sized_struct .try_into() .expect("push constants are NoBools and NoHandles"); let layout = TypeLayout::new_storage_layout_for(sized_struct); let layout = match &layout.kind { - TypeLayoutSemantics::Structure(layout) => &**layout, + type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, _ => unreachable!("expected struct layout for type layout of struct"), }; diff --git a/shame/src/lib.rs b/shame/src/lib.rs index b54475b..84bdcbd 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -316,11 +316,7 @@ pub use shame_derive::CpuLayout; pub use shame_derive::GpuLayout; pub use frontend::rust_types::layout_traits::GpuLayout; pub use frontend::rust_types::layout_traits::CpuLayout; -pub use frontend::rust_types::type_layout; pub use frontend::rust_types::type_layout::TypeLayout; -pub use frontend::rust_types::type_layout::TypeLayoutError; -pub use frontend::rust_types::type_layout::cpu_shareable; -pub use frontend::rust_types::layout_traits::ArrayElementsUnsizedError; // derived traits pub use frontend::rust_types::type_traits::GpuStore; @@ -465,6 +461,61 @@ pub mod any { pub use crate::ir::ir_type::StructureDefinitionError; pub use crate::ir::ir_type::StructureFieldNamesMustBeUnique; + pub mod layout { + // type layout + pub use crate::frontend::rust_types::type_layout::TypeLayout; + pub use crate::frontend::rust_types::type_layout::TypeRepr; + pub use crate::frontend::rust_types::type_layout::Repr; + pub mod repr { + pub use crate::frontend::rust_types::type_layout::repr::Plain; + pub use crate::frontend::rust_types::type_layout::repr::Storage; + pub use crate::frontend::rust_types::type_layout::repr::Uniform; + pub use crate::frontend::rust_types::type_layout::repr::Packed; + } + pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; + pub use crate::frontend::rust_types::type_layout::StructLayout; + pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; + pub use crate::frontend::rust_types::type_layout::FieldLayout; + pub use crate::frontend::rust_types::type_layout::FieldOptions; + pub use crate::frontend::rust_types::type_layout::ElementLayout; + // layoutable traits + pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; + pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; + // layoutable types + pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; + pub use crate::frontend::rust_types::type_layout::layoutable::UnsizedStruct; + pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArray; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; + pub use crate::frontend::rust_types::type_layout::layoutable::Vector; + pub use crate::frontend::rust_types::type_layout::layoutable::Matrix; + pub use crate::frontend::rust_types::type_layout::layoutable::MatrixMajor; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedArray; + pub use crate::frontend::rust_types::type_layout::layoutable::Atomic; + pub use crate::frontend::rust_types::type_layout::layoutable::PackedVector; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; + // layoutable type parts + pub use crate::frontend::rust_types::type_layout::layoutable::ScalarType; + pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeFp; + pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeInteger; + pub use crate::frontend::rust_types::type_layout::layoutable::Len; + pub use crate::frontend::rust_types::type_layout::layoutable::Len2; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedField; + pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArrayField; + pub use crate::frontend::rust_types::type_layout::layoutable::CanonName; + pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; + // layout calculation utility + pub use crate::frontend::rust_types::type_layout::LayoutCalculator; + pub use crate::frontend::rust_types::type_layout::layoutable::array_stride; + pub use crate::frontend::rust_types::type_layout::layoutable::array_size; + pub use crate::frontend::rust_types::type_layout::layoutable::array_align; + pub use crate::frontend::rust_types::type_layout::layoutable::FieldOffsets; + // conversion and builder errors + pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableConversionError; + pub use crate::frontend::rust_types::type_layout::layoutable::ContainsBoolsError; + pub use crate::frontend::rust_types::type_layout::layoutable::builder::IsUnsizedStructError; + pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; + } + // runtime binding api pub use any::shared_io::BindPath; pub use any::shared_io::BindingType; diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 855d8a9..0b5cb9f 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -248,9 +248,9 @@ fn unsized_struct_vec3_align_layout_eq() { } assert_ne!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); assert!(OnGpu::gpu_layout().byte_size() == Some(16)); - assert!(OnGpu::gpu_layout().byte_align().as_u32() == 16); + assert!(OnGpu::gpu_layout().align().as_u32() == 16); assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().byte_align().as_u32() == 4); + assert!(OnCpu::cpu_layout().align().as_u32() == 4); } #[test] diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 45a1446..64a795e 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -200,8 +200,8 @@ pub fn impl_for_struct( match which_derive { WhichDerive::GpuLayout => { - let layout_type_fn = quote! { - let result = #re::LayoutType::struct_from_parts( + let layoutable_type_fn = quote! { + let result = #re::LayoutableType::struct_from_parts( std::stringify!(#derive_struct_ident), [ #(( @@ -210,13 +210,13 @@ pub fn impl_for_struct( #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), #field_size.into(), ), - <#field_type as #re::BinaryRepr>::layout_type() + <#field_type as #re::Layoutable>::layoutable_type() ),)* ] ); match result { - Ok(layout_type) => layout_type, + Ok(layoutable_type) => layoutable_type, Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), // GpuType is not implemented for derived structs directly, so they can't be used @@ -226,28 +226,28 @@ pub fn impl_for_struct( } }; - let impl_binary_repr = quote! { - impl<#generics_decl> #re::BinaryRepr for #derive_struct_ident<#(#idents_of_generics),*> + let impl_layoutable = quote! { + impl<#generics_decl> #re::Layoutable for #derive_struct_ident<#(#idents_of_generics),*> where - // These NoBools and NoHandle bounds are only for better diagnostics, BinaryRepr already implies them - #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::BinaryReprSized,)* - #last_field_type: #re::NoBools + #re::NoHandles + #re::BinaryRepr, + // These NoBools and NoHandle bounds are only for better diagnostics, Layoutable already implies them + #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::LayoutableSized,)* + #last_field_type: #re::NoBools + #re::NoHandles + #re::Layoutable, #where_clause_predicates { - fn layout_type() -> #re::LayoutType { - #layout_type_fn + fn layoutable_type() -> #re::LayoutableType { + #layoutable_type_fn } } - impl<#generics_decl> #re::BinaryReprSized for #derive_struct_ident<#(#idents_of_generics),*> + impl<#generics_decl> #re::LayoutableSized for #derive_struct_ident<#(#idents_of_generics),*> where - #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::BinaryReprSized,)* + #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::LayoutableSized,)* #where_clause_predicates { - fn layout_type_sized() -> #re::SizedType { - match { #layout_type_fn } { - #re::LayoutType::Sized(s) => s, - _ => unreachable!("ensured by BinaryReprSized field trait bounds above") + fn layoutable_type_sized() -> #re::SizedType { + match { #layoutable_type_fn } { + #re::LayoutableType::Sized(s) => s, + _ => unreachable!("ensured by LayoutableSized field trait bounds above") } } } @@ -256,8 +256,8 @@ pub fn impl_for_struct( let impl_gpu_layout = quote! { impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> where - #(#first_fields_type: #re::BinaryReprSized,)* - #last_field_type: #re::BinaryRepr, + #(#first_fields_type: #re::LayoutableSized,)* + #last_field_type: #re::Layoutable, #where_clause_predicates { fn gpu_repr() -> #re::Repr { @@ -365,7 +365,7 @@ pub fn impl_for_struct( // this is basically only for vertex buffers, so // we only implement `GpuLayout` and `VertexLayout`, as well as their implied traits Ok(quote! { - #impl_binary_repr + #impl_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits @@ -380,7 +380,7 @@ pub fn impl_for_struct( ); Ok(quote! { - #impl_binary_repr + #impl_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits From 8420a3c374b6839b975b1ae59fc8209ffc1cec69 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 03:39:00 +0200 Subject: [PATCH 007/182] Small import change --- shame/src/frontend/any/render_io.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 4b267f1..93e817d 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,9 +2,8 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; -use crate::any::layout; use crate::frontend::any::Any; -use crate::frontend::rust_types::type_layout::TypeLayout; +use crate::frontend::rust_types::type_layout::{layoutable, TypeLayout}; use crate::{ call_info, common::iterator_ext::try_collect, @@ -109,7 +108,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, layout::ScalarType), + Fine(Len, layoutable::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -252,7 +251,7 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - layout::array_stride(layout.align(), size) + layoutable::array_stride(layout.align(), size) }; use TypeLayoutSemantics as TLS; From 6ce7e49ad51c4a768ab40a0bc1f0adec500bff0f Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 03:43:18 +0200 Subject: [PATCH 008/182] Fix example name --- examples/type_layout/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/type_layout/Cargo.toml b/examples/type_layout/Cargo.toml index 9d53846..dd64124 100644 --- a/examples/type_layout/Cargo.toml +++ b/examples/type_layout/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "type_layout2" +name = "type_layout" version = "0.1.0" edition = "2024" From c5b9088be4e4cc8ffe0bd2f758db696596838150 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 04:14:27 +0200 Subject: [PATCH 009/182] PackedVector to ir type conversion --- .../src/frontend/rust_types/type_layout/layoutable/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 4a28875..230e2b6 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -291,8 +291,12 @@ impl From for ir::SizedType { SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), SizedType::Array(a) => ir::SizedType::Array(Rc::new(Rc::unwrap_or_clone(a.element).into()), a.len), SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), - // TODO(chronicl) check if this should be decompressed - SizedType::PackedVec(v) => v.decompressed_ty(), + SizedType::PackedVec(v) => SizedType::Vector(match v.byte_size() { + ir::ir_type::PackedVectorByteSize::_2 => Vector::new(ScalarType::F16, Len::X1), + ir::ir_type::PackedVectorByteSize::_4 => Vector::new(ScalarType::U32, Len::X1), + ir::ir_type::PackedVectorByteSize::_8 => Vector::new(ScalarType::U32, Len::X2), + }) + .into(), SizedType::Struct(s) => ir::SizedType::Structure(s.into()), } } From bdcf1573e41dd0f54679cd96453155b0db08c67c Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 15:19:43 +0200 Subject: [PATCH 010/182] Remove alignment rounding duplication --- .../rust_types/type_layout/layoutable/align_size.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 76b62dd..868f7fa 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -92,14 +92,12 @@ impl Iterator for FieldOffsets<'_> { match self.repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage | Repr::Packed => (size, align), - // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // The uniform address space requires that: - // - struct S align: roundUp(16, AlignOf(S)) // - If a structure member itself has a structure type S, then the number of // bytes between the start of that member and the start of any following // member must be at least roundUp(16, SizeOf(S)). - // -> We adjust size too. - Repr::Uniform => (round_up(16, size), round_up_align(U32PowerOf2::_16, align)), + // -> We need to adjust size. + Repr::Uniform => (round_up(16, size), align), } } non_struct => non_struct.byte_size_and_align(self.repr), From f3690d03c05320ce4768c8e4b841e158f80d0d53 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 17:38:29 +0200 Subject: [PATCH 011/182] Add byte_size and align for LayoutableType --- .../rust_types/type_layout/builder.rs | 2 +- .../type_layout/layoutable/align_size.rs | 53 +++++++++++++++---- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index a3bfd53..ab7b1f0 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -141,7 +141,7 @@ impl TypeLayout { fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { Self::new( None, - ty.byte_align(repr), + ty.align(repr), TLS::Array( Rc::new(ElementLayout { byte_stride: ty.byte_stride(repr), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 868f7fa..97683e9 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -4,6 +4,37 @@ use super::*; // Size and align of layoutable types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // +impl LayoutableType { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn byte_size(&self, repr: Repr) -> Option { + match self { + LayoutableType::Sized(s) => Some(s.byte_size(repr)), + LayoutableType::UnsizedStruct(_) | LayoutableType::RuntimeSizedArray(_) => None, + } + } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + match self { + LayoutableType::Sized(s) => s.align(repr), + LayoutableType::UnsizedStruct(s) => s.align(repr), + LayoutableType::RuntimeSizedArray(a) => a.align(repr), + } + } + + /// This is expensive for structs as it calculates the byte size and align from scratch. + pub fn byte_size_and_align(&self, repr: Repr) -> (Option, U32PowerOf2) { + match self { + LayoutableType::Sized(s) => { + let (size, align) = s.byte_size_and_align(repr); + (Some(size), align) + } + LayoutableType::UnsizedStruct(s) => (None, s.align(repr)), + LayoutableType::RuntimeSizedArray(a) => (None, a.align(repr)), + } + } +} + impl SizedType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, repr: Repr) -> u64 { @@ -17,8 +48,8 @@ impl SizedType { } } - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(), @@ -29,11 +60,11 @@ impl SizedType { } } - /// This is expensive for structs. + /// This is expensive for structs as it calculates the byte size and align from scratch. pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { match self { SizedType::Struct(s) => s.byte_size_and_align(repr), - non_struct => (non_struct.byte_size(repr), non_struct.byte_align(repr)), + non_struct => (non_struct.byte_size(repr), non_struct.align(repr)), } } } @@ -140,14 +171,14 @@ impl UnsizedStruct { // Iterating over any remaining field offsets to update the layout calculator. (&mut offsets).count(); - let array_align = self.last_unsized.array.byte_align(offsets.repr); + let array_align = self.last_unsized.array.align(offsets.repr); let custom_min_align = self.last_unsized.custom_min_align; let (offset, align) = offsets.calc.extend_unsized(array_align, custom_min_align); (offset, struct_align(align, offsets.repr)) } /// This is expensive as it calculates the byte align from scratch. - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { + pub fn align(&self, repr: Repr) -> U32PowerOf2 { let offsets = self.sized_field_offsets(repr); self.last_field_offset_and_struct_align(offsets).1 } @@ -242,7 +273,7 @@ impl Atomic { impl SizedArray { pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } pub fn byte_stride(&self, repr: Repr) -> u64 { let (element_size, element_align) = self.element.byte_size_and_align(repr); @@ -273,20 +304,20 @@ pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 #[allow(missing_docs)] impl RuntimeSizedArray { - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.byte_align(repr), repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } - pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.byte_align(repr), self.element.byte_size(repr)) } + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr)) } } #[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.byte_align(repr) } + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } } #[allow(missing_docs)] impl RuntimeSizedArrayField { - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.byte_align(repr) } + pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } } pub const fn round_up(multiple_of: u64, n: u64) -> u64 { From e255e1b364521d54a75e3490cd910672fb162080 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 22:12:20 +0200 Subject: [PATCH 012/182] Make default use_color false --- shame/src/frontend/rust_types/type_layout/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index ab7b1f0..4ee2898 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -249,7 +249,7 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { u_layout, // TODO(chronicl) default shouldn't be true? use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(true), + .unwrap_or(false), }; use UniformLayoutError as U; From 3f1dcf7c15cab6ee9e32ebf7b54843a69091a13b Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 22:24:52 +0200 Subject: [PATCH 013/182] Decouple gpu_layout from GpuLayout --- examples/api_showcase/src/main.rs | 37 +++++----- .../hello_triangles/src/util/shame_glam.rs | 8 +- examples/type_layout/src/main.rs | 9 ++- .../src/frontend/rust_types/layout_traits.rs | 54 ++++++++------ shame/src/frontend/rust_types/packed_vec.rs | 4 +- shame/src/lib.rs | 2 + shame/tests/test_layout.rs | 73 ++++++++++--------- 7 files changed, 100 insertions(+), 87 deletions(-) diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index f8e2d88..0d25789 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -1,15 +1,16 @@ #![allow(unused, clippy::no_effect)] +use shame::gpu_layout; use shame as sm; use shame::prelude::*; use shame::aliases::*; #[rustfmt::skip] fn make_pipeline(some_param: u32) -> Result { - + // start a pipeline encoding with the default settings. // (in the `shame_wgpu` examples, this is wrapped by the `Gpu` object) - // - // compared to earlier versions of `shame`, pipeline + // + // compared to earlier versions of `shame`, pipeline // encoding is no longer based on a closure, but based on a // RAII guard `sm::EncodingGuard<...>` instead. // That way you can use the `?` operator for your own @@ -60,7 +61,7 @@ fn make_pipeline(some_param: u32) -> Result Result Result = group0.next(); let xforms_uni: sm::Buffer = group0.next(); - + // conditional code generation based on pipeline parameter if some_param > 0 { // if not further specified, defaults to `sm::mem::Storage` @@ -99,7 +100,7 @@ fn make_pipeline(some_param: u32) -> Result Result Result Result Result Result Result().set_with_alpha_to_coverage(rg); + // targets.next::().set_with_alpha_to_coverage(rg); // finish the encoding and obtain the pipeline setup info + shader code. encoder.finish() @@ -486,13 +487,13 @@ struct Mat2([[f32; 2]; 2]); // tell `shame` about the layout semantics of your cpu types // Mat2::layout() == sm::f32x2x2::layout() impl sm::CpuLayout for Mat2 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x2x2::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } #[repr(C, align(16))] struct Mat4([[f32; 4]; 4]); impl sm::CpuLayout for Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } // using "duck-traiting" allows you to define layouts for foreign cpu-types, @@ -509,4 +510,4 @@ impl sm::CpuLayout for Mat4 { // // tell `shame` about the layout semantics of `glam` types // impl MyCpuLayoutTrait for glam::Mat4 { // fn layout() -> shame::TypeLayout { sm::f32x4x4::layout() } -// } \ No newline at end of file +// } diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index 7f85092..faebf6c 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -2,7 +2,7 @@ //! //! implement `shame::CpuLayout` for some glam types. -use shame as sm; +use shame::{self as sm, gpu_layout}; use sm::GpuLayout; /// circumventing the orphan rule by defining our own trait. @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - sm::f32x2::gpu_layout() + gpu_layout::() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { sm::f32x4x4::gpu_layout() } + fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 56bfbc0..11b99aa 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -6,12 +6,13 @@ use shame::{ any::{ self, layout::{ - self, LayoutableSized, UnsizedStruct, SizedField, SizedType, ScalarType, Len, RuntimeSizedArrayField, - FieldOptions, Vector, + self, FieldOptions, LayoutableSized, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, + UnsizedStruct, Vector, }, U32PowerOf2, }, - boolx1, f32x1, f32x2, f32x3, f32x4, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, VertexLayout, + boolx1, f32x1, f32x2, f32x3, f32x4, gpu_layout, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, + VertexLayout, }; fn main() { @@ -118,7 +119,7 @@ fn main() { } // this would be 8 for storage layout rules - assert!(D::gpu_layout().align.as_u32() == 16); + assert!(gpu_layout::().align.as_u32() == 16); // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 3a4741c..bb2bdff 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -138,28 +138,6 @@ use std::rc::Rc; /// [`StorageTexture`]: crate::StorageTexture /// pub trait GpuLayout: Layoutable { - /// returns a [`TypeLayout`] object that can be used to inspect the layout - /// of a type on the gpu. - /// - /// # Layout comparison of different types - /// - /// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared - /// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` - /// ``` - /// use shame as sm; - /// use sm::{ GpuLayout, CpuLayout }; - /// - /// type OnGpu = sm::Array>; - /// type OnCpu = [f32; 16]; - /// - /// if OnGpu::gpu_layout() == OnCpu::cpu_layout() { - /// println!("same layout") - /// } - /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); - /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); - /// ``` - fn gpu_layout() -> TypeLayout { TypeLayout::new_layout_for(Self::layoutable_type(), Self::gpu_repr()) } - /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. fn gpu_repr() -> Repr; @@ -176,6 +154,36 @@ pub trait GpuLayout: Layoutable { fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>>; } +/// returns a [`TypeLayout`] object that can be used to inspect the layout +/// of a type on the gpu. +/// +/// # Layout comparison of different types +/// +/// The layouts of different [`GpuLayout`]/[`CpuLayout`] types can be compared +/// by comparing [`TypeLayout`] objects returned by `.gpu_layout()`/`.cpu_layout()` +/// ``` +/// use shame as sm; +/// use sm::{ GpuLayout, CpuLayout }; +/// +/// type OnGpu = sm::Array>; +/// type OnCpu = [f32; 16]; +/// +/// if OnGpu::gpu_layout() == OnCpu::cpu_layout() { +/// println!("same layout") +/// } +/// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); +/// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); +/// ``` +pub fn gpu_layout() -> TypeLayout { + TypeLayout::new_layout_for(T::layoutable_type(), T::gpu_repr()) +} + +/// (no documentation yet) +// `CpuLayout::cpu_layout` exists, but this function exists for consistency with +// the `gpu_layout` function. `GpuLayout::gpu_layout` does not exist, so that implementors +// of `GpuLayout` can't overwrite it. +pub fn cpu_layout() -> TypeLayout { T::cpu_layout() } + pub(crate) fn cpu_type_name_and_layout(ctx: &Context) -> Option<(Cow<'static, str>, TypeLayout)> { match T::cpu_type_name_and_layout().transpose() { Ok(t) => t, @@ -199,7 +207,7 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( ) -> TypeLayout { const ERR_COMMENT: &str = "`GpuLayout` uses WGSL layout rules unless #[gpu_repr(packed)] is used.\nsee https://www.w3.org/TR/WGSL/#structure-member-layout\n`CpuLayout` uses #[repr(C)].\nsee https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.struct"; - let gpu_layout = T::gpu_layout(); + let gpu_layout = gpu_layout::(); if let Some((cpu_name, cpu_layout)) = cpu_type_name_and_layout::(ctx) { check_layout_push_error(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT).ok(); } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 8682fe9..596a0bb 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -10,7 +10,7 @@ use crate::{ AsAny, DataPackingFn, }, common::floating_point::f16, - f32x2, f32x4, i32x4, u32x1, u32x4, + f32x2, f32x4, gpu_layout, i32x4, u32x1, u32x4, }; use crate::frontend::rust_types::len::{x1, x2, x3, x4}; use crate::frontend::rust_types::vec::vec; @@ -172,7 +172,7 @@ impl From for PackedVec { let inner = Context::try_with(call_info!(), |ctx| { let err = |ty| { ctx.push_error_get_invalid_any( - FrontendError::InvalidDowncastToNonShaderType(ty, Self::gpu_layout()).into(), + FrontendError::InvalidDowncastToNonShaderType(ty, gpu_layout::()).into(), ) }; match any.ty() { diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 84bdcbd..3501272 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -315,7 +315,9 @@ pub use frontend::texture::texture_formats as tf; pub use shame_derive::CpuLayout; pub use shame_derive::GpuLayout; pub use frontend::rust_types::layout_traits::GpuLayout; +pub use frontend::rust_types::layout_traits::gpu_layout; pub use frontend::rust_types::layout_traits::CpuLayout; +pub use frontend::rust_types::layout_traits::cpu_layout; pub use frontend::rust_types::type_layout::TypeLayout; // derived traits diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 0b5cb9f..041d20d 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types, unused)] use pretty_assertions::{assert_eq, assert_ne}; -use shame as sm; +use shame::{self as sm, cpu_layout, gpu_layout}; use sm::{aliases::*, CpuLayout, GpuLayout}; #[test] @@ -21,7 +21,7 @@ fn basic_layout_eq() { c: i32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -50,9 +50,9 @@ fn attributes_dont_contribute_to_eq() { c: i32, } - assert_eq!(OnGpuA::gpu_layout(), OnCpu::cpu_layout()); - assert_eq!(OnGpuB::gpu_layout(), OnCpu::cpu_layout()); - assert_eq!(OnGpuA::gpu_layout(), OnGpuB::gpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), gpu_layout::()); } #[test] @@ -74,7 +74,7 @@ fn fixed_by_align_size_attribute() { c: i32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } { @@ -94,7 +94,7 @@ fn fixed_by_align_size_attribute() { c: f32x3_size32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } } @@ -115,7 +115,7 @@ fn different_align_struct_eq() { c: i32, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -135,7 +135,7 @@ fn unsized_struct_layout_eq() { c: [i32], } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[derive(Clone, Copy)] @@ -147,21 +147,21 @@ struct f32x4_cpu(pub [f32; 4]); struct f32x3_cpu(pub [f32; 3]); impl CpuLayout for f32x3_cpu { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] #[repr(C, align(8))] struct f32x2_cpu(pub [f32; 2]); impl CpuLayout for f32x2_cpu { - fn cpu_layout() -> shame::TypeLayout { f32x2::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] #[repr(C)] struct f32x2_align4(pub [f32; 2]); impl CpuLayout for f32x2_align4 { - fn cpu_layout() -> shame::TypeLayout { f32x2::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] @@ -169,7 +169,7 @@ impl CpuLayout for f32x2_align4 { struct f32x4_align4(pub [f32; 4]); impl CpuLayout for f32x4_align4 { - fn cpu_layout() -> shame::TypeLayout { f32x4::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] @@ -182,7 +182,7 @@ static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); impl CpuLayout for f32x3_align4 { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } #[derive(Clone, Copy)] @@ -190,7 +190,7 @@ impl CpuLayout for f32x3_align4 { struct f32x3_size32(pub [f32; 3], [u8; 20]); impl CpuLayout for f32x3_size32 { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } @@ -212,7 +212,7 @@ fn unsized_struct_vec3_align_layout_eq() { c: [f32x3_cpu], } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -231,7 +231,7 @@ fn unsized_struct_vec3_align_layout_eq() { // the alignment on the top level of the layout doesn't matter. // two layouts are only considered different if an alignment mismatch // leads to different offsets of fields or array elements - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -246,11 +246,11 @@ fn unsized_struct_vec3_align_layout_eq() { struct OnCpu { // size=12, align=4 a: f32x3_align4, } - assert_ne!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); - assert!(OnGpu::gpu_layout().byte_size() == Some(16)); - assert!(OnGpu::gpu_layout().align().as_u32() == 16); - assert!(OnCpu::cpu_layout().byte_size() == Some(12)); - assert!(OnCpu::cpu_layout().align().as_u32() == 4); + assert_ne!(gpu_layout::(), cpu_layout::()); + assert!(gpu_layout::().byte_size() == Some(16)); + assert!(gpu_layout::().align().as_u32() == 16); + assert!(cpu_layout::().byte_size() == Some(12)); + assert!(cpu_layout::().align().as_u32() == 4); } #[test] @@ -283,15 +283,15 @@ fn unsized_struct_nested_vec3_align_layout_eq() { c: [InnerCpu], } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] fn unsized_array_layout_eq() { - assert_eq!(>::gpu_layout(), <[f32]>::cpu_layout()); - assert_eq!(>::gpu_layout(), <[f32x3_cpu]>::cpu_layout()); - assert_ne!(>::gpu_layout(), <[f32x3_align4]>::cpu_layout()); - assert_ne!(>::gpu_layout(), <[f32x3_size32]>::cpu_layout()); + assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); + assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); } #[test] @@ -318,14 +318,15 @@ fn layouts_mismatch() { c: i32, } - assert_ne!(OnGpuLess::gpu_layout(), OnCpu::cpu_layout()); - assert_ne!(OnGpuMore::gpu_layout(), OnCpu::cpu_layout()); + assert_ne!(gpu_layout::(), cpu_layout::()); + assert_ne!(gpu_layout::(), cpu_layout::()); } #[test] fn external_vec_type() { // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` pub mod my_mod { + use shame::gpu_layout; use shame as sm; use sm::aliases::*; use sm::GpuLayout as _; @@ -335,11 +336,11 @@ fn external_vec_type() { } impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> shame::TypeLayout { f32x4::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } impl CpuLayoutExt for glam::Vec3 { - fn cpu_layout() -> shame::TypeLayout { f32x3::gpu_layout() } + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } } } @@ -357,7 +358,7 @@ fn external_vec_type() { b: glam::Vec4, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); #[derive(sm::GpuLayout)] struct OnGpu2 { @@ -384,8 +385,8 @@ fn external_vec_type() { c: glam::Vec4, } - assert_ne!(OnGpu2::gpu_layout(), OnCpu2::cpu_layout()); - assert_eq!(OnGpu2Packed::gpu_layout(), OnCpu2::cpu_layout()); + assert_ne!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -407,7 +408,7 @@ fn external_vec_type() { uv : f32x2_align4, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); } { #[derive(sm::GpuLayout)] @@ -426,7 +427,7 @@ fn external_vec_type() { uv : f32x2_cpu, } - assert_eq!(OnGpu::gpu_layout(), OnCpu::cpu_layout()); + assert_eq!(gpu_layout::(), cpu_layout::()); enum __ where OnGpu: sm::VertexLayout {} } } From e19e85b8be3ab7500a500086045a1d3d0b22b927 Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 23 May 2025 22:29:41 +0200 Subject: [PATCH 014/182] Move Display impl of ScalarType --- .../rust_types/type_layout/layoutable/mod.rs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 230e2b6..61b188a 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -191,19 +191,6 @@ pub trait LayoutableSized: Layoutable { fn layoutable_type_sized() -> SizedType; } - -impl std::fmt::Display for ScalarType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - ScalarType::F16 => "f16", - ScalarType::F32 => "f32", - ScalarType::F64 => "f64", - ScalarType::U32 => "u32", - ScalarType::I32 => "i32", - }) - } -} - // Conversions to ScalarType, SizedType and LayoutableType // macro_rules! impl_into_sized_type { @@ -536,3 +523,15 @@ impl std::fmt::Display for SizedType { } } } + +impl std::fmt::Display for ScalarType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ScalarType::F16 => "f16", + ScalarType::F32 => "f32", + ScalarType::F64 => "f64", + ScalarType::U32 => "u32", + ScalarType::I32 => "i32", + }) + } +} From e2f41cda846e10ea30568f13a3df75d403309500 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 02:35:17 +0200 Subject: [PATCH 015/182] Make TryFrom for StoreType fallible started --- .../rust_types/type_layout/builder.rs | 14 +- .../type_layout/layoutable/ir_compat.rs | 498 ++++++++++++++++++ .../rust_types/type_layout/layoutable/mod.rs | 290 ++-------- .../frontend/rust_types/type_layout/mod.rs | 4 +- shame/src/lib.rs | 90 ++-- 5 files changed, 595 insertions(+), 301 deletions(-) create mode 100644 shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index 4ee2898..8e758c8 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -318,7 +318,7 @@ pub struct ArrayStrideError { impl std::error::Error for WithContext {} impl Display for WithContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level: StoreType = self.ctx.s_layout.layoutable_type().clone().into(); + let top_level = self.ctx.s_layout.layoutable_type(); writeln!( f, "array elements within type `{}` do not satisfy uniform layout requirements.", @@ -327,9 +327,7 @@ impl Display for WithContext { writeln!( f, "The array with `{}` elements requires stride {}, but has stride {}.", - Into::::into(self.element_ty.clone()), - self.expected, - self.actual + self.element_ty, self.expected, self.actual )?; writeln!(f, "The full layout of `{}` is:", top_level)?; self.ctx.s_layout.write("", self.ctx.use_color, f)?; @@ -451,10 +449,7 @@ where let struct_name = &*struct_layout.name; let indent = " "; - let field_decl_line = |field: &SizedField| { - let sized: ir::SizedType = field.ty.clone().into(); - format!("{indent}{}: {},", field.name, sized) - }; + let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); let header = format!("struct {} {{", struct_name); let table_start_column = 1 + sized_fields .iter() @@ -487,8 +482,7 @@ where if let Some(last_field) = last_unsized { let layout = struct_layout.fields.last().expect("structs have at least one field"); - let store_type: ir::StoreType = last_field.array.clone().into(); - let decl_line = format!("{indent}{}: {},", last_field.name, store_type); + let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); f.write_str(&decl_line)?; // write spaces to table on the right for _ in decl_line.len()..table_start_column { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs new file mode 100644 index 0000000..98315b3 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -0,0 +1,498 @@ +use super::*; + +// Conversions to ir types // + +/// Errors that can occur when converting IR types to layoutable types. +#[derive(thiserror::Error, Debug)] +pub enum IRConversionError { + /// Packed vectors do not exist in the shader type system. + #[error("{0}")] + ContainsPackedVector(#[from] ContainsPackedVectorError), + /// Struct field names must be unique in the shader type system. + #[error("{0}")] + DuplicateFieldName(#[from] DuplicateFieldNameError), +} + +#[derive(Debug)] +pub enum ContainsPackedVectorError { + /// Top level type is a packed vector. + SelfIsPackedVector(LayoutableType), + /// Top level nested Array packed vector, for example Array>>. + InArray(LayoutableType), + /// A struct field is a packed vector or a struct field is a nested Array packed vector. + InStruct { + struct_type: StructKind, + packed_vector_field: usize, + top_level_type: Option, + use_color: bool, + }, +} + +impl std::error::Error for ContainsPackedVectorError {} + +impl std::fmt::Display for ContainsPackedVectorError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let ending = + ", which does not exist in the shader type system.\nPacked vectors may only be used in vertex buffers."; + let (struct_name, sized_fields, last_unsized, packed_vector_field, use_color) = match self { + ContainsPackedVectorError::SelfIsPackedVector(t) => return writeln!(f, "{t} is a packed vector{ending}"), + ContainsPackedVectorError::InArray(a) => return writeln!(f, "{a} contains a packed vector{ending}"), + ContainsPackedVectorError::InStruct { + struct_type, + packed_vector_field, + top_level_type, + use_color, + } => { + let (struct_name, sized_fields, last_unsized) = match struct_type { + StructKind::Sized(s) => (&s.name, s.fields(), None), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + + if let Some(top_level_type) = top_level_type { + writeln!( + f, + "The struct {struct_name} in {top_level_type} contains a packed vector{ending}" + )?; + } else { + writeln!(f, "{struct_name} contains a packed vector{ending}")?; + } + + ( + struct_name, + sized_fields, + last_unsized, + *packed_vector_field, + *use_color, + ) + } + }; + + + + let indent = " "; + let is_packed_vec = |i| packed_vector_field == i; + let arrow = |i| match is_packed_vec(i) { + true => " <--", + false => "", + }; + let color = |f: &mut Formatter<'_>, i| { + if (use_color && is_packed_vec(i)) { + set_color(f, Some("#508EE3"), false) + } else { + Ok(()) + } + }; + let color_reset = |f: &mut Formatter<'_>, i| { + if use_color && is_packed_vec(i) { + set_color(f, None, false) + } else { + Ok(()) + } + }; + + writeln!(f, "The following struct contains a packed vector:")?; + let header = writeln!(f, "struct {} {{", struct_name); + for (i, field) in sized_fields.iter().enumerate() { + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; + color_reset(f, i)?; + } + if let Some(field) = last_unsized { + let i = sized_fields.len(); + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.array, arrow(i))?; + color_reset(f, i)?; + } + writeln!(f, "}}")?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct DuplicateFieldNameError { + pub struct_type: StructKind, + pub first_field: usize, + pub second_field: usize, + pub is_top_level: bool, + pub use_color: bool, +} + +impl std::error::Error for DuplicateFieldNameError {} + +impl std::fmt::Display for DuplicateFieldNameError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let (struct_name, sized_fields, last_unsized) = match &self.struct_type { + StructKind::Sized(s) => (&s.name, s.fields(), None), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + + let indent = " "; + let is_duplicate = |i| self.first_field == i || self.second_field == i; + let arrow = |i| match is_duplicate(i) { + true => " <--", + false => "", + }; + let color = |f: &mut Formatter<'_>, i| { + if (self.use_color && is_duplicate(i)) { + set_color(f, Some("#508EE3"), false) + } else { + Ok(()) + } + }; + let color_reset = |f: &mut Formatter<'_>, i| { + if self.use_color && is_duplicate(i) { + set_color(f, None, false) + } else { + Ok(()) + } + }; + + writeln!( + f, + "Type contains or is a struct with duplicate field names.\ + Field names must be unique in the shader type system.\n\ + The following struct contains duplicate field names:" + )?; + let header = writeln!(f, "struct {} {{", struct_name); + for (i, field) in sized_fields.iter().enumerate() { + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; + color_reset(f, i)?; + } + if let Some(field) = last_unsized { + let i = sized_fields.len(); + color(f, i)?; + writeln!(f, "{indent}{}: {},{}", field.name, field.array, arrow(i))?; + color_reset(f, i)?; + } + writeln!(f, "}}")?; + + Ok(()) + } +} + +fn check_for_duplicate_field_names( + struct_type: StructKind, + is_top_level: bool, +) -> Result { + let (sized_fields, last_unsized) = match &struct_type { + StructKind::Sized(s) => (s.fields(), None), + StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), + }; + + // Brute force search > HashMap for the amount of fields + // we'd usually deal with. + let mut duplicate_fields = None; + for (i, field1) in sized_fields.iter().enumerate() { + for (j, field2) in sized_fields[i..].iter().enumerate() { + if field1.name == field2.name { + duplicate_fields = Some((i, j)); + break; + } + } + } + + match duplicate_fields { + Some((first_field, second_field)) => Err(DuplicateFieldNameError { + struct_type, + first_field, + second_field, + is_top_level, + use_color: use_color(), + }), + None => Ok(struct_type), + } +} + +#[track_caller] +fn use_color() -> bool { Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false) } + +impl TryFrom for ir::StoreType { + type Error = IRConversionError; + + fn try_from(ty: LayoutableType) -> Result { + // Checking for top level packed vec. + if matches!(ty, LayoutableType::Sized(SizedType::PackedVec(_))) { + return Err(IRConversionError::ContainsPackedVector( + ContainsPackedVectorError::SelfIsPackedVector(ty), + )); + } + // Checking for nested array packed vec. + if is_packed_vec(&ty) { + return Err(IRConversionError::ContainsPackedVector( + ContainsPackedVectorError::InArray(ty), + )); + } + + fn convert + + todo!() + } +} + +/// Returns true if `ty` is a packed vector or a nesting of Arrays +/// with the last Array directly containing a packed vector. +fn is_packed_vec(ty: &LayoutableType) -> bool { + let mut ty = match ty { + LayoutableType::Sized(s) => s, + LayoutableType::RuntimeSizedArray(a) => &a.element, + LayoutableType::UnsizedStruct(_) => return false, + }; + + loop { + match ty { + SizedType::PackedVec(_) => return true, + SizedType::Array(a) => ty = &a.element, + _ => return false, + } + } +} + +// impl TryFrom for ir::SizedType { +// type Error = IRConversionError; + +// fn try_from(host: SizedType) -> Result { +// // Ok(match host { +// // SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), +// // SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), +// // SizedType::Array(a) => { +// // let element = Rc::unwrap_or_clone(a.element); +// // let converted_element = element.try_into()?; +// // ir::SizedType::Array(Rc::new(converted_element), a.len) +// // } +// // SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), +// // SizedType::PackedVec(_) => return Err(IRConversionError::ContainsPackedVector), +// // SizedType::Struct(s) => ir::SizedType::Structure(s.try_into()?), +// // }) +// todo!() +// } +// } + +impl From for ir::ScalarType { + fn from(scalar_type: ScalarType) -> Self { + match scalar_type { + ScalarType::F16 => ir::ScalarType::F16, + ScalarType::F32 => ir::ScalarType::F32, + ScalarType::F64 => ir::ScalarType::F64, + ScalarType::U32 => ir::ScalarType::U32, + ScalarType::I32 => ir::ScalarType::I32, + } + } +} + +// impl TryFrom for ir::ir_type::SizedStruct { +// type Error = IRConversionError; + +// fn try_from(host: SizedStruct) -> Result { +// let mut fields: Vec = Vec::new(); + +// for field in host.fields { +// fields.push(field.try_into()?); +// } + +// // has at least one field +// if fields.is_empty() { +// // This should not happen based on SizedStruct's implementation +// return Err(IRConversionError::DuplicateFieldName); +// } + +// let last_field = fields.pop().unwrap(); + +// match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { +// Ok(s) => Ok(s), +// Err(StructureFieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), +// } +// } +// } + +// impl TryFrom for ir::ir_type::BufferBlock { +// type Error = IRConversionError; + +// fn try_from(host: UnsizedStruct) -> Result { +// let mut sized_fields: Vec = Vec::new(); + +// for field in host.sized_fields { +// sized_fields.push(field.try_into()?); +// } + +// let last_unsized = host.last_unsized.try_into()?; + +// match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { +// Ok(b) => Ok(b), +// Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), +// Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { +// // This should not happen based on UnsizedStruct's implementation +// Err(IRConversionError::DuplicateFieldName) +// } +// } +// } +// } + +// impl TryFrom for ir::StoreType { +// type Error = IRConversionError; + +// fn try_from(array: RuntimeSizedArray) -> Result { +// Ok(ir::StoreType::RuntimeSizedArray(array.element.try_into()?)) +// } +// } + +// impl TryFrom for ir::ir_type::SizedField { +// type Error = IRConversionError; + +// fn try_from(f: SizedField) -> Result { +// Ok(ir::SizedField::new( +// f.name, +// f.custom_min_size, +// f.custom_min_align, +// f.ty.try_into()?, +// )) +// } +// } + +// impl TryFrom for ir::ir_type::RuntimeSizedArrayField { +// type Error = IRConversionError; + +// fn try_from(f: RuntimeSizedArrayField) -> Result { +// Ok(ir::RuntimeSizedArrayField::new( +// f.name, +// f.custom_min_align, +// f.array.element.try_into()?, +// )) +// } +// } + + +// Conversions from ir types // + +/// Type contains bools, which doesn't have a known layout. +#[derive(thiserror::Error, Debug)] +#[error("Type contains bools, which doesn't have a known layout.")] +pub struct ContainsBoolsError; + +/// Errors that can occur when converting IR types to layoutable types. +#[derive(thiserror::Error, Debug)] +pub enum LayoutableConversionError { + /// Type contains bools, which don't have a standardized memory layout. + #[error("Type contains bools, which don't have a standardized memory layout.")] + ContainsBool, + /// Type is a handle, which don't have a standardized memory layout. + #[error("Type is a handle, which don't have a standardized memory layout.")] + IsHandle, +} + +impl TryFrom for ScalarType { + type Error = ContainsBoolsError; + + fn try_from(value: ir::ScalarType) -> Result { + Ok(match value { + ir::ScalarType::F16 => ScalarType::F16, + ir::ScalarType::F32 => ScalarType::F32, + ir::ScalarType::F64 => ScalarType::F64, + ir::ScalarType::U32 => ScalarType::U32, + ir::ScalarType::I32 => ScalarType::I32, + ir::ScalarType::Bool => return Err(ContainsBoolsError), + }) + } +} + +impl TryFrom for SizedType { + type Error = ContainsBoolsError; + + fn try_from(value: ir::SizedType) -> Result { + Ok(match value { + ir::SizedType::Vector(len, scalar) => SizedType::Vector(Vector { + scalar: scalar.try_into()?, + len, + }), + ir::SizedType::Matrix(columns, rows, scalar) => SizedType::Matrix(Matrix { scalar, columns, rows }), + ir::SizedType::Array(element, len) => SizedType::Array(SizedArray { + element: Rc::new((*element).clone().try_into()?), + len, + }), + ir::SizedType::Atomic(scalar_type) => SizedType::Atomic(Atomic { scalar: scalar_type }), + ir::SizedType::Structure(structure) => SizedType::Struct(structure.try_into()?), + }) + } +} + +impl TryFrom for SizedStruct { + type Error = ContainsBoolsError; + + fn try_from(structure: ir::ir_type::SizedStruct) -> Result { + let mut fields = Vec::new(); + + for field in structure.sized_fields() { + fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + Ok(SizedStruct { + name: structure.name().clone(), + fields, + }) + } +} + +impl From for LayoutableConversionError { + fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } +} + +impl TryFrom for LayoutableType { + type Error = LayoutableConversionError; + + fn try_from(value: ir::StoreType) -> Result { + Ok(match value { + ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { + element: element.try_into()?, + }), + ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, + ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), + }) + } +} + +impl TryFrom for LayoutableType { + type Error = ContainsBoolsError; + + fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { + let mut sized_fields = Vec::new(); + + for field in buffer_block.sized_fields() { + sized_fields.push(SizedField { + name: field.name.clone(), + custom_min_size: field.custom_min_size, + custom_min_align: field.custom_min_align, + ty: field.ty.clone().try_into()?, + }); + } + + let last_unsized = if let Some(last_field) = buffer_block.last_unsized_field() { + RuntimeSizedArrayField { + name: last_field.name.clone(), + custom_min_align: last_field.custom_min_align, + array: RuntimeSizedArray { + element: last_field.element_ty.clone().try_into()?, + }, + } + } else { + return Ok(SizedStruct { + name: buffer_block.name().clone(), + fields: sized_fields, + } + .into()); + }; + + Ok(UnsizedStruct { + name: buffer_block.name().clone(), + sized_fields, + last_unsized, + } + .into()) + } +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 61b188a..8016763 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -1,17 +1,20 @@ //! This module defines types that can be laid out in memory. -use std::{num::NonZeroU32, rc::Rc}; +use std::{fmt::Formatter, num::NonZeroU32, rc::Rc}; use crate::{ any::U32PowerOf2, - ir::{self, ir_type::BufferBlockDefinitionError, StructureFieldNamesMustBeUnique}, + call_info, + common::prettify::set_color, + ir::{self, ir_type::BufferBlockDefinitionError, recording::Context, StructureFieldNamesMustBeUnique}, }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::FieldOptions; +use super::{builder::StructKind, FieldOptions}; pub(crate) mod align_size; pub(crate) mod builder; +pub(crate) mod ir_compat; pub use align_size::{FieldOffsets, MatrixMajor, array_size, array_stride, array_align}; pub use builder::SizedOrArray; @@ -258,274 +261,69 @@ impl From for ScalarType { fn from(int: ScalarTypeFp) -> Self { int.as_scalar_type() } } +// Display impls -// Conversions to ir types // - -impl From for ir::StoreType { - fn from(host: LayoutableType) -> Self { - match host { - LayoutableType::Sized(s) => ir::StoreType::Sized(s.into()), - LayoutableType::RuntimeSizedArray(s) => ir::StoreType::RuntimeSizedArray(s.element.into()), - LayoutableType::UnsizedStruct(s) => ir::StoreType::BufferBlock(s.into()), +impl std::fmt::Display for LayoutableType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + LayoutableType::Sized(s) => s.fmt(f), + LayoutableType::RuntimeSizedArray(a) => a.fmt(f), + LayoutableType::UnsizedStruct(s) => s.fmt(f), } } } -impl From for ir::SizedType { - fn from(host: SizedType) -> Self { - match host { - SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), - SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), - SizedType::Array(a) => ir::SizedType::Array(Rc::new(Rc::unwrap_or_clone(a.element).into()), a.len), - SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), - SizedType::PackedVec(v) => SizedType::Vector(match v.byte_size() { - ir::ir_type::PackedVectorByteSize::_2 => Vector::new(ScalarType::F16, Len::X1), - ir::ir_type::PackedVectorByteSize::_4 => Vector::new(ScalarType::U32, Len::X1), - ir::ir_type::PackedVectorByteSize::_8 => Vector::new(ScalarType::U32, Len::X2), - }) - .into(), - SizedType::Struct(s) => ir::SizedType::Structure(s.into()), +impl std::fmt::Display for SizedType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SizedType::Vector(v) => v.fmt(f), + SizedType::Matrix(m) => m.fmt(f), + SizedType::Array(a) => a.fmt(f), + SizedType::Atomic(a) => a.fmt(f), + SizedType::PackedVec(p) => p.fmt(f), + SizedType::Struct(s) => s.fmt(f), } } } -impl From for ir::ScalarType { - fn from(scalar_type: ScalarType) -> Self { - match scalar_type { - ScalarType::F16 => ir::ScalarType::F16, - ScalarType::F32 => ir::ScalarType::F32, - ScalarType::F64 => ir::ScalarType::F64, - ScalarType::U32 => ir::ScalarType::U32, - ScalarType::I32 => ir::ScalarType::I32, - } - } +impl std::fmt::Display for Vector { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}x{}", self.scalar, self.len) } } -impl From for ir::ir_type::SizedStruct { - fn from(host: SizedStruct) -> Self { - let mut fields: Vec = host.fields.into_iter().map(Into::into).collect(); - // has at least one field - let last_field = fields.pop().unwrap(); - - // Note: This might throw an error in real usage if the fields aren't valid - // We're assuming they're valid in the conversion - match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { - Ok(s) => s, - Err(StructureFieldNamesMustBeUnique) => { - // TODO(chronicl) this isn't true - unreachable!("field names are unique for `LayoutType`") - } - } +impl std::fmt::Display for Matrix { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "mat<{}, {}, {}>", + self.scalar, + Len::from(self.columns), + Len::from(self.rows) + ) } } -impl From for ir::ir_type::BufferBlock { - fn from(host: UnsizedStruct) -> Self { - let sized_fields: Vec = host.sized_fields.into_iter().map(Into::into).collect(); - - let last_unsized = host.last_unsized.into(); - - // TODO(chronicl) - // Note: This might throw an error in real usage if the struct isn't valid - // We're assuming it's valid in the conversion - match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { - Ok(b) => b, - Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => { - // TODO(chronicl) this isn't true - unreachable!("field names are unique for `UnsizedStruct`") - } - Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { - unreachable!("`UnsizedStruct` has at least one field.") - } - } - } +impl std::fmt::Display for SizedArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Array<{}, {}>", &*self.element, self.len) } } -impl From for ir::StoreType { - fn from(array: RuntimeSizedArray) -> Self { ir::StoreType::RuntimeSizedArray(array.element.into()) } +impl std::fmt::Display for Atomic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Atomic<{}>", ScalarType::from(self.scalar)) } } -impl From for ir::ir_type::SizedField { - fn from(f: SizedField) -> Self { ir::SizedField::new(f.name, f.custom_min_size, f.custom_min_align, f.ty.into()) } +impl std::fmt::Display for SizedStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } -impl From for ir::ir_type::RuntimeSizedArrayField { - fn from(f: RuntimeSizedArrayField) -> Self { - ir::RuntimeSizedArrayField::new(f.name, f.custom_min_align, f.array.element.into()) - } +impl std::fmt::Display for UnsizedStruct { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } - -// Conversions from ir types // - -/// Type contains bools, which doesn't have a known layout. -#[derive(thiserror::Error, Debug)] -#[error("Type contains bools, which doesn't have a known layout.")] -pub struct ContainsBoolsError; - -impl TryFrom for ScalarType { - type Error = ContainsBoolsError; - - fn try_from(value: ir::ScalarType) -> Result { - Ok(match value { - ir::ScalarType::F16 => ScalarType::F16, - ir::ScalarType::F32 => ScalarType::F32, - ir::ScalarType::F64 => ScalarType::F64, - ir::ScalarType::U32 => ScalarType::U32, - ir::ScalarType::I32 => ScalarType::I32, - ir::ScalarType::Bool => return Err(ContainsBoolsError), - }) - } -} - -impl TryFrom for SizedType { - type Error = ContainsBoolsError; - - fn try_from(value: ir::SizedType) -> Result { - Ok(match value { - ir::SizedType::Vector(len, scalar) => SizedType::Vector(Vector { - scalar: scalar.try_into()?, - len, - }), - ir::SizedType::Matrix(columns, rows, scalar) => SizedType::Matrix(Matrix { scalar, columns, rows }), - ir::SizedType::Array(element, len) => SizedType::Array(SizedArray { - element: Rc::new((*element).clone().try_into()?), - len, - }), - ir::SizedType::Atomic(scalar_type) => SizedType::Atomic(Atomic { scalar: scalar_type }), - ir::SizedType::Structure(structure) => SizedType::Struct(structure.try_into()?), - }) - } -} - -impl TryFrom for SizedStruct { - type Error = ContainsBoolsError; - - fn try_from(structure: ir::ir_type::SizedStruct) -> Result { - let mut fields = Vec::new(); - - for field in structure.sized_fields() { - fields.push(SizedField { - name: field.name.clone(), - custom_min_size: field.custom_min_size, - custom_min_align: field.custom_min_align, - ty: field.ty.clone().try_into()?, - }); - } - - Ok(SizedStruct { - name: structure.name().clone(), - fields, - }) - } -} - -/// Errors that can occur when converting IR types to layoutable types. -#[derive(thiserror::Error, Debug)] -pub enum LayoutableConversionError { - /// Type contains bools, which don't have a standardized memory layout. - #[error("Type contains bools, which don't have a standardized memory layout.")] - ContainsBool, - /// Type is a handle, which don't have a standardized memory layout. - #[error("Type is a handle, which don't have a standardized memory layout.")] - IsHandle, -} - -impl From for LayoutableConversionError { - fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } -} - -impl TryFrom for LayoutableType { - type Error = LayoutableConversionError; - - fn try_from(value: ir::StoreType) -> Result { - Ok(match value { - ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), - ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { - element: element.try_into()?, - }), - ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, - ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), - }) - } -} - -impl TryFrom for LayoutableType { - type Error = ContainsBoolsError; - - fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { - let mut sized_fields = Vec::new(); - - for field in buffer_block.sized_fields() { - sized_fields.push(SizedField { - name: field.name.clone(), - custom_min_size: field.custom_min_size, - custom_min_align: field.custom_min_align, - ty: field.ty.clone().try_into()?, - }); - } - - let last_unsized = if let Some(last_field) = buffer_block.last_unsized_field() { - RuntimeSizedArrayField { - name: last_field.name.clone(), - custom_min_align: last_field.custom_min_align, - array: RuntimeSizedArray { - element: last_field.element_ty.clone().try_into()?, - }, - } - } else { - return Ok(SizedStruct { - name: buffer_block.name().clone(), - fields: sized_fields, - } - .into()); - }; - - Ok(UnsizedStruct { - name: buffer_block.name().clone(), - sized_fields, - last_unsized, - } - .into()) - } -} - -// Display impls - -impl std::fmt::Display for LayoutableType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LayoutableType::Sized(s) => write!(f, "{s}"), - LayoutableType::RuntimeSizedArray(a) => write!(f, "Array<{}>", a.element), - LayoutableType::UnsizedStruct(s) => write!(f, "{}", s.name), - } - } -} - -impl std::fmt::Display for SizedType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SizedType::Vector(v) => write!(f, "{}{}", ir::ScalarType::from(v.scalar), v.len), - SizedType::Matrix(m) => { - write!( - f, - "mat<{}, {}, {}>", - ir::ScalarType::from(m.scalar), - Len::from(m.columns), - Len::from(m.rows) - ) - } - SizedType::Array(a) => write!(f, "Array<{}, {}>", &*a.element, a.len), - SizedType::Atomic(a) => write!(f, "Atomic<{}>", ir::ScalarType::from(a.scalar)), - // TODO(chronicl) figure out scalar type display - SizedType::PackedVec(p) => write!(f, "PackedVec<{:?}, {}>", p.scalar_type, Len::from(p.len)), - SizedType::Struct(s) => write!(f, "{}", s.name), - } - } +impl std::fmt::Display for RuntimeSizedArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Array<{}>", self.element) } } impl std::fmt::Display for ScalarType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(match self { ScalarType::F16 => "f16", ScalarType::F32 => "f32", diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 9acbe90..d46fce2 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -475,7 +475,9 @@ impl TypeLayout { } // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings - pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + pub(crate) fn from_store_ty( + store_type: ir::StoreType, + ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; Ok(TypeLayout::new_storage_layout_for(t).into()) } diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 3501272..ed8770f 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -464,58 +464,60 @@ pub mod any { pub use crate::ir::ir_type::StructureFieldNamesMustBeUnique; pub mod layout { + use crate::frontend::rust_types::type_layout; // type layout - pub use crate::frontend::rust_types::type_layout::TypeLayout; - pub use crate::frontend::rust_types::type_layout::TypeRepr; - pub use crate::frontend::rust_types::type_layout::Repr; + pub use type_layout::TypeLayout; + pub use type_layout::TypeRepr; + pub use type_layout::Repr; pub mod repr { - pub use crate::frontend::rust_types::type_layout::repr::Plain; - pub use crate::frontend::rust_types::type_layout::repr::Storage; - pub use crate::frontend::rust_types::type_layout::repr::Uniform; - pub use crate::frontend::rust_types::type_layout::repr::Packed; + use crate::frontend::rust_types::type_layout; + pub use type_layout::repr::Plain; + pub use type_layout::repr::Storage; + pub use type_layout::repr::Uniform; + pub use type_layout::repr::Packed; } - pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; - pub use crate::frontend::rust_types::type_layout::StructLayout; - pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; - pub use crate::frontend::rust_types::type_layout::FieldLayout; - pub use crate::frontend::rust_types::type_layout::FieldOptions; - pub use crate::frontend::rust_types::type_layout::ElementLayout; + pub use type_layout::TypeLayoutSemantics; + pub use type_layout::StructLayout; + pub use type_layout::FieldLayoutWithOffset; + pub use type_layout::FieldLayout; + pub use type_layout::FieldOptions; + pub use type_layout::ElementLayout; // layoutable traits - pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; - pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; + pub use type_layout::layoutable::Layoutable; + pub use type_layout::layoutable::LayoutableSized; // layoutable types - pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; - pub use crate::frontend::rust_types::type_layout::layoutable::UnsizedStruct; - pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArray; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; - pub use crate::frontend::rust_types::type_layout::layoutable::Vector; - pub use crate::frontend::rust_types::type_layout::layoutable::Matrix; - pub use crate::frontend::rust_types::type_layout::layoutable::MatrixMajor; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedArray; - pub use crate::frontend::rust_types::type_layout::layoutable::Atomic; - pub use crate::frontend::rust_types::type_layout::layoutable::PackedVector; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; + pub use type_layout::layoutable::LayoutableType; + pub use type_layout::layoutable::UnsizedStruct; + pub use type_layout::layoutable::RuntimeSizedArray; + pub use type_layout::layoutable::SizedType; + pub use type_layout::layoutable::Vector; + pub use type_layout::layoutable::Matrix; + pub use type_layout::layoutable::MatrixMajor; + pub use type_layout::layoutable::SizedArray; + pub use type_layout::layoutable::Atomic; + pub use type_layout::layoutable::PackedVector; + pub use type_layout::layoutable::SizedStruct; // layoutable type parts - pub use crate::frontend::rust_types::type_layout::layoutable::ScalarType; - pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeFp; - pub use crate::frontend::rust_types::type_layout::layoutable::ScalarTypeInteger; - pub use crate::frontend::rust_types::type_layout::layoutable::Len; - pub use crate::frontend::rust_types::type_layout::layoutable::Len2; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedField; - pub use crate::frontend::rust_types::type_layout::layoutable::RuntimeSizedArrayField; - pub use crate::frontend::rust_types::type_layout::layoutable::CanonName; - pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; + pub use type_layout::layoutable::ScalarType; + pub use type_layout::layoutable::ScalarTypeFp; + pub use type_layout::layoutable::ScalarTypeInteger; + pub use type_layout::layoutable::Len; + pub use type_layout::layoutable::Len2; + pub use type_layout::layoutable::SizedField; + pub use type_layout::layoutable::RuntimeSizedArrayField; + pub use type_layout::layoutable::CanonName; + pub use type_layout::layoutable::SizedOrArray; // layout calculation utility - pub use crate::frontend::rust_types::type_layout::LayoutCalculator; - pub use crate::frontend::rust_types::type_layout::layoutable::array_stride; - pub use crate::frontend::rust_types::type_layout::layoutable::array_size; - pub use crate::frontend::rust_types::type_layout::layoutable::array_align; - pub use crate::frontend::rust_types::type_layout::layoutable::FieldOffsets; + pub use type_layout::LayoutCalculator; + pub use type_layout::layoutable::array_stride; + pub use type_layout::layoutable::array_size; + pub use type_layout::layoutable::array_align; + pub use type_layout::layoutable::FieldOffsets; // conversion and builder errors - pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableConversionError; - pub use crate::frontend::rust_types::type_layout::layoutable::ContainsBoolsError; - pub use crate::frontend::rust_types::type_layout::layoutable::builder::IsUnsizedStructError; - pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; + pub use type_layout::layoutable::ir_compat::LayoutableConversionError; + pub use type_layout::layoutable::ir_compat::ContainsBoolsError; + pub use type_layout::layoutable::builder::IsUnsizedStructError; + pub use type_layout::layoutable::builder::StructFromPartsError; } // runtime binding api From 1aecb7e3725535c4b2d9af3bd628756377ffe715 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 02:57:09 +0200 Subject: [PATCH 016/182] Make ContainsPackedVecError simple --- .../type_layout/layoutable/ir_compat.rs | 370 ++++++------------ 1 file changed, 123 insertions(+), 247 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 98315b3..fd23aa2 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -6,115 +6,21 @@ use super::*; #[derive(thiserror::Error, Debug)] pub enum IRConversionError { /// Packed vectors do not exist in the shader type system. - #[error("{0}")] - ContainsPackedVector(#[from] ContainsPackedVectorError), + #[error( + "Type is or contains a packed vector, which does not exist in the shader type system.\n\ + Packed vectors may only be used in vertex buffers." + )] + ContainsPackedVector, /// Struct field names must be unique in the shader type system. #[error("{0}")] DuplicateFieldName(#[from] DuplicateFieldNameError), } -#[derive(Debug)] -pub enum ContainsPackedVectorError { - /// Top level type is a packed vector. - SelfIsPackedVector(LayoutableType), - /// Top level nested Array packed vector, for example Array>>. - InArray(LayoutableType), - /// A struct field is a packed vector or a struct field is a nested Array packed vector. - InStruct { - struct_type: StructKind, - packed_vector_field: usize, - top_level_type: Option, - use_color: bool, - }, -} - -impl std::error::Error for ContainsPackedVectorError {} - -impl std::fmt::Display for ContainsPackedVectorError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let ending = - ", which does not exist in the shader type system.\nPacked vectors may only be used in vertex buffers."; - let (struct_name, sized_fields, last_unsized, packed_vector_field, use_color) = match self { - ContainsPackedVectorError::SelfIsPackedVector(t) => return writeln!(f, "{t} is a packed vector{ending}"), - ContainsPackedVectorError::InArray(a) => return writeln!(f, "{a} contains a packed vector{ending}"), - ContainsPackedVectorError::InStruct { - struct_type, - packed_vector_field, - top_level_type, - use_color, - } => { - let (struct_name, sized_fields, last_unsized) = match struct_type { - StructKind::Sized(s) => (&s.name, s.fields(), None), - StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), Some(&s.last_unsized)), - }; - - if let Some(top_level_type) = top_level_type { - writeln!( - f, - "The struct {struct_name} in {top_level_type} contains a packed vector{ending}" - )?; - } else { - writeln!(f, "{struct_name} contains a packed vector{ending}")?; - } - - ( - struct_name, - sized_fields, - last_unsized, - *packed_vector_field, - *use_color, - ) - } - }; - - - - let indent = " "; - let is_packed_vec = |i| packed_vector_field == i; - let arrow = |i| match is_packed_vec(i) { - true => " <--", - false => "", - }; - let color = |f: &mut Formatter<'_>, i| { - if (use_color && is_packed_vec(i)) { - set_color(f, Some("#508EE3"), false) - } else { - Ok(()) - } - }; - let color_reset = |f: &mut Formatter<'_>, i| { - if use_color && is_packed_vec(i) { - set_color(f, None, false) - } else { - Ok(()) - } - }; - - writeln!(f, "The following struct contains a packed vector:")?; - let header = writeln!(f, "struct {} {{", struct_name); - for (i, field) in sized_fields.iter().enumerate() { - color(f, i)?; - writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; - color_reset(f, i)?; - } - if let Some(field) = last_unsized { - let i = sized_fields.len(); - color(f, i)?; - writeln!(f, "{indent}{}: {},{}", field.name, field.array, arrow(i))?; - color_reset(f, i)?; - } - writeln!(f, "}}")?; - - Ok(()) - } -} - #[derive(Debug)] pub struct DuplicateFieldNameError { pub struct_type: StructKind, pub first_field: usize, pub second_field: usize, - pub is_top_level: bool, pub use_color: bool, } @@ -173,14 +79,9 @@ impl std::fmt::Display for DuplicateFieldNameError { } fn check_for_duplicate_field_names( - struct_type: StructKind, - is_top_level: bool, -) -> Result { - let (sized_fields, last_unsized) = match &struct_type { - StructKind::Sized(s) => (s.fields(), None), - StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), - }; - + sized_fields: &[SizedField], + last_unsized: Option<&RuntimeSizedArrayField>, +) -> Option<(usize, usize)> { // Brute force search > HashMap for the amount of fields // we'd usually deal with. let mut duplicate_fields = None; @@ -191,18 +92,14 @@ fn check_for_duplicate_field_names( break; } } + if let Some(last_unsized) = last_unsized { + if field1.name == last_unsized.name { + duplicate_fields = Some((i, sized_fields.len())); + break; + } + } } - - match duplicate_fields { - Some((first_field, second_field)) => Err(DuplicateFieldNameError { - struct_type, - first_field, - second_field, - is_top_level, - use_color: use_color(), - }), - None => Ok(struct_type), - } + duplicate_fields } #[track_caller] @@ -212,63 +109,33 @@ impl TryFrom for ir::StoreType { type Error = IRConversionError; fn try_from(ty: LayoutableType) -> Result { - // Checking for top level packed vec. - if matches!(ty, LayoutableType::Sized(SizedType::PackedVec(_))) { - return Err(IRConversionError::ContainsPackedVector( - ContainsPackedVectorError::SelfIsPackedVector(ty), - )); - } - // Checking for nested array packed vec. - if is_packed_vec(&ty) { - return Err(IRConversionError::ContainsPackedVector( - ContainsPackedVectorError::InArray(ty), - )); + match ty { + LayoutableType::Sized(s) => Ok(ir::StoreType::Sized(s.try_into()?)), + LayoutableType::RuntimeSizedArray(s) => Ok(ir::StoreType::RuntimeSizedArray(s.element.try_into()?)), + LayoutableType::UnsizedStruct(s) => Ok(ir::StoreType::BufferBlock(s.try_into()?)), } - - fn convert - - todo!() } } -/// Returns true if `ty` is a packed vector or a nesting of Arrays -/// with the last Array directly containing a packed vector. -fn is_packed_vec(ty: &LayoutableType) -> bool { - let mut ty = match ty { - LayoutableType::Sized(s) => s, - LayoutableType::RuntimeSizedArray(a) => &a.element, - LayoutableType::UnsizedStruct(_) => return false, - }; +impl TryFrom for ir::SizedType { + type Error = IRConversionError; - loop { - match ty { - SizedType::PackedVec(_) => return true, - SizedType::Array(a) => ty = &a.element, - _ => return false, - } + fn try_from(host: SizedType) -> Result { + Ok(match host { + SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), + SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), + SizedType::Array(a) => { + let element = Rc::unwrap_or_clone(a.element); + let converted_element = element.try_into()?; + ir::SizedType::Array(Rc::new(converted_element), a.len) + } + SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), + SizedType::PackedVec(_) => return Err(IRConversionError::ContainsPackedVector), + SizedType::Struct(s) => ir::SizedType::Structure(s.try_into()?), + }) } } -// impl TryFrom for ir::SizedType { -// type Error = IRConversionError; - -// fn try_from(host: SizedType) -> Result { -// // Ok(match host { -// // SizedType::Vector(v) => ir::SizedType::Vector(v.len, v.scalar.into()), -// // SizedType::Matrix(m) => ir::SizedType::Matrix(m.columns, m.rows, m.scalar), -// // SizedType::Array(a) => { -// // let element = Rc::unwrap_or_clone(a.element); -// // let converted_element = element.try_into()?; -// // ir::SizedType::Array(Rc::new(converted_element), a.len) -// // } -// // SizedType::Atomic(i) => ir::SizedType::Atomic(i.scalar), -// // SizedType::PackedVec(_) => return Err(IRConversionError::ContainsPackedVector), -// // SizedType::Struct(s) => ir::SizedType::Structure(s.try_into()?), -// // }) -// todo!() -// } -// } - impl From for ir::ScalarType { fn from(scalar_type: ScalarType) -> Self { match scalar_type { @@ -281,86 +148,95 @@ impl From for ir::ScalarType { } } -// impl TryFrom for ir::ir_type::SizedStruct { -// type Error = IRConversionError; - -// fn try_from(host: SizedStruct) -> Result { -// let mut fields: Vec = Vec::new(); - -// for field in host.fields { -// fields.push(field.try_into()?); -// } - -// // has at least one field -// if fields.is_empty() { -// // This should not happen based on SizedStruct's implementation -// return Err(IRConversionError::DuplicateFieldName); -// } - -// let last_field = fields.pop().unwrap(); - -// match ir::ir_type::SizedStruct::new_nonempty(host.name, fields, last_field) { -// Ok(s) => Ok(s), -// Err(StructureFieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), -// } -// } -// } - -// impl TryFrom for ir::ir_type::BufferBlock { -// type Error = IRConversionError; - -// fn try_from(host: UnsizedStruct) -> Result { -// let mut sized_fields: Vec = Vec::new(); - -// for field in host.sized_fields { -// sized_fields.push(field.try_into()?); -// } - -// let last_unsized = host.last_unsized.try_into()?; - -// match ir::ir_type::BufferBlock::new(host.name, sized_fields, Some(last_unsized)) { -// Ok(b) => Ok(b), -// Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => Err(IRConversionError::DuplicateFieldName), -// Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { -// // This should not happen based on UnsizedStruct's implementation -// Err(IRConversionError::DuplicateFieldName) -// } -// } -// } -// } - -// impl TryFrom for ir::StoreType { -// type Error = IRConversionError; - -// fn try_from(array: RuntimeSizedArray) -> Result { -// Ok(ir::StoreType::RuntimeSizedArray(array.element.try_into()?)) -// } -// } - -// impl TryFrom for ir::ir_type::SizedField { -// type Error = IRConversionError; - -// fn try_from(f: SizedField) -> Result { -// Ok(ir::SizedField::new( -// f.name, -// f.custom_min_size, -// f.custom_min_align, -// f.ty.try_into()?, -// )) -// } -// } - -// impl TryFrom for ir::ir_type::RuntimeSizedArrayField { -// type Error = IRConversionError; - -// fn try_from(f: RuntimeSizedArrayField) -> Result { -// Ok(ir::RuntimeSizedArrayField::new( -// f.name, -// f.custom_min_align, -// f.array.element.try_into()?, -// )) -// } -// } +impl TryFrom for ir::ir_type::SizedStruct { + type Error = IRConversionError; + + fn try_from(ty: SizedStruct) -> Result { + if let Some((first, second)) = check_for_duplicate_field_names(ty.fields(), None) { + return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Sized(ty), + first_field: first, + second_field: second, + use_color: use_color(), + })); + } + + let mut fields: Vec = Vec::new(); + for field in ty.fields { + fields.push(field.try_into()?); + } + let last_field = fields.pop().unwrap(); + + match ir::ir_type::SizedStruct::new_nonempty(ty.name, fields, last_field) { + Ok(s) => Ok(s), + Err(StructureFieldNamesMustBeUnique) => unreachable!("checked above"), + } + } +} + +impl TryFrom for ir::ir_type::BufferBlock { + type Error = IRConversionError; + + fn try_from(ty: UnsizedStruct) -> Result { + if let Some((first, second)) = check_for_duplicate_field_names(&ty.sized_fields, Some(&ty.last_unsized)) { + return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Unsized(ty), + first_field: first, + second_field: second, + use_color: use_color(), + })); + } + + let mut sized_fields: Vec = Vec::new(); + + for field in ty.sized_fields { + sized_fields.push(field.try_into()?); + } + + let last_unsized = ty.last_unsized.try_into()?; + + match ir::ir_type::BufferBlock::new(ty.name, sized_fields, Some(last_unsized)) { + Ok(b) => Ok(b), + Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => unreachable!("checked above"), + Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { + unreachable!("last_unsized is at least one field") + } + } + } +} + +impl TryFrom for ir::StoreType { + type Error = IRConversionError; + + fn try_from(array: RuntimeSizedArray) -> Result { + Ok(ir::StoreType::RuntimeSizedArray(array.element.try_into()?)) + } +} + +impl TryFrom for ir::ir_type::SizedField { + type Error = IRConversionError; + + fn try_from(f: SizedField) -> Result { + Ok(ir::SizedField::new( + f.name, + f.custom_min_size, + f.custom_min_align, + f.ty.try_into()?, + )) + } +} + +impl TryFrom for ir::ir_type::RuntimeSizedArrayField { + type Error = IRConversionError; + + fn try_from(f: RuntimeSizedArrayField) -> Result { + Ok(ir::RuntimeSizedArrayField::new( + f.name, + f.custom_min_align, + f.array.element.try_into()?, + )) + } +} // Conversions from ir types // From 38b535e72992ab946407f7a2d87d9a6d09e26b8e Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 03:09:25 +0200 Subject: [PATCH 017/182] Test LayoutableType -> StoreType conversion --- .../type_layout/layoutable/ir_compat.rs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index fd23aa2..81fd987 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -86,9 +86,9 @@ fn check_for_duplicate_field_names( // we'd usually deal with. let mut duplicate_fields = None; for (i, field1) in sized_fields.iter().enumerate() { - for (j, field2) in sized_fields[i..].iter().enumerate() { + for (j, field2) in sized_fields.iter().skip(i + 1).enumerate() { if field1.name == field2.name { - duplicate_fields = Some((i, j)); + duplicate_fields = Some((i, i + 1 + j)); break; } } @@ -372,3 +372,28 @@ impl TryFrom for LayoutableType { .into()) } } + + +#[test] +fn test_ir_conversion_error() { + use crate::{f32x1, packed::unorm8x2}; + + let ty: LayoutableType = SizedStruct::new("A", "a", f32x1::layoutable_type_sized()) + .extend("b", f32x1::layoutable_type_sized()) + .extend("a", f32x1::layoutable_type_sized()) + .into(); + let result: Result = ty.try_into(); + assert!(matches!( + result, + Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Sized(_), + first_field: 0, + second_field: 2, + .. + })) + )); + + let ty: LayoutableType = SizedStruct::new("A", "a", unorm8x2::layoutable_type_sized()).into(); + let result: Result = ty.try_into(); + assert!(matches!(result, Err(IRConversionError::ContainsPackedVector))); +} From f49aa5b415f3e1acbdcdffc4acb0b3ee79983eb9 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 03:53:43 +0200 Subject: [PATCH 018/182] Make align of all types 1 when Repr::Packed --- .../rust_types/type_layout/builder.rs | 11 +- .../src/frontend/rust_types/type_layout/eq.rs | 4 +- .../type_layout/layoutable/align_size.rs | 66 +++++---- .../frontend/rust_types/type_layout/mod.rs | 127 +++++++++--------- shame/src/ir/ir_type/tensor.rs | 7 +- 5 files changed, 111 insertions(+), 104 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs index 8e758c8..fdaaaa7 100644 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/builder.rs @@ -64,14 +64,14 @@ impl TypeLayout { fn from_sized_type(ty: SizedType, repr: Repr) -> Self { let (size, align, tls) = match &ty { - SizedType::Vector(v) => (v.byte_size(), v.align(), TLS::Vector(*v)), + SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), SizedType::Atomic(a) => ( a.byte_size(), - a.align(), + a.align(repr), TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), ), SizedType::Matrix(m) => ( - m.byte_size(MatrixMajor::Row), + m.byte_size(repr, MatrixMajor::Row), m.align(repr, MatrixMajor::Row), TLS::Matrix(*m), ), @@ -86,7 +86,7 @@ impl TypeLayout { Some(a.len.get()), ), ), - SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(), TLS::PackedVector(*v)), + SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), SizedType::Struct(s) => { let mut field_offsets = s.field_offsets(repr); let fields = (&mut field_offsets) @@ -137,7 +137,6 @@ impl TypeLayout { ) } - fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { Self::new( None, @@ -206,7 +205,7 @@ impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { field_name: s_field.field.name.clone(), field_index: i, actual_offset: s_field.rel_byte_offset, - expected_alignment: u_field.field.byte_align(), + expected_alignment: u_field.field.align(), is_top_level, })); } diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index c194666..5ece418 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -129,7 +129,7 @@ impl LayoutMismatch { "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", a_field.rel_byte_offset, a_field.field.name, - a_field.field.byte_align().as_u32() + a_field.field.align().as_u32() ); color_b(f); writeln!( @@ -137,7 +137,7 @@ impl LayoutMismatch { "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", b_field.rel_byte_offset, b_field.field.name, - b_field.field.byte_align().as_u32() + b_field.field.align().as_u32() ); color_reset(f); writeln!( diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 97683e9..e080a97 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -4,6 +4,8 @@ use super::*; // Size and align of layoutable types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // +pub(crate) const PACKED_ALIGN: U32PowerOf2 = U32PowerOf2::_1; + impl LayoutableType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, repr: Repr) -> Option { @@ -41,7 +43,7 @@ impl SizedType { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(MatrixMajor::Row), + SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Row), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -52,10 +54,10 @@ impl SizedType { pub fn align(&self, repr: Repr) -> U32PowerOf2 { match self { SizedType::Array(a) => a.align(repr), - SizedType::Vector(v) => v.align(), + SizedType::Vector(v) => v.align(repr), SizedType::Matrix(m) => m.align(repr, MatrixMajor::Row), - SizedType::Atomic(a) => a.align(), - SizedType::PackedVec(v) => v.align(), + SizedType::Atomic(a) => a.align(repr), + SizedType::PackedVec(v) => v.align(repr), SizedType::Struct(s) => s.byte_size_and_align(repr).1, } } @@ -79,12 +81,12 @@ impl SizedStruct { FieldOffsets { fields: &self.fields, field_index: 0, - calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + calc: LayoutCalculator::new(repr), repr, } } - /// Returns (byte_size, byte_align) + /// Returns (byte_size, align) /// /// ### Careful! /// This is an expensive operation as it calculates byte size and align from scratch. @@ -117,25 +119,11 @@ impl Iterator for FieldOffsets<'_> { fn next(&mut self) -> Option { self.field_index += 1; self.fields.get(self.field_index - 1).map(|field| { - let (size, align) = match &field.ty { - SizedType::Struct(s) => { - let (size, align) = s.byte_size_and_align(self.repr); - match self.repr { - // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage | Repr::Packed => (size, align), - // The uniform address space requires that: - // - If a structure member itself has a structure type S, then the number of - // bytes between the start of that member and the start of any following - // member must be at least roundUp(16, SizeOf(S)). - // -> We need to adjust size. - Repr::Uniform => (round_up(16, size), align), - } - } - non_struct => non_struct.byte_size_and_align(self.repr), - }; + let (size, align) = field.ty.byte_size_and_align(self.repr); + let is_struct = matches!(field.ty, SizedType::Struct(_)); self.calc - .extend(size, align, field.custom_min_size, field.custom_min_align) + .extend(size, align, field.custom_min_size, field.custom_min_align, is_struct) }) } } @@ -157,7 +145,7 @@ impl UnsizedStruct { FieldOffsets { fields: &self.sized_fields, field_index: 0, - calc: LayoutCalculator::new(matches!(repr, Repr::Packed)), + calc: LayoutCalculator::new(repr), repr, } } @@ -187,8 +175,9 @@ impl UnsizedStruct { const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage | Repr::Packed => align, + Repr::Storage => align, Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + Repr::Packed => PACKED_ALIGN, } } @@ -198,12 +187,15 @@ impl Vector { pub const fn byte_size(&self) -> u64 { self.len.as_u64() * self.scalar.byte_size() } - pub const fn align(&self) -> U32PowerOf2 { + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + let len = match self.len { Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), Len::X3 => 4, }; - // len * self.scalar.align() = power of 2 * power of 2 = power of 2 U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()).unwrap() } @@ -237,15 +229,15 @@ pub enum MatrixMajor { #[allow(missing_docs)] impl Matrix { - pub const fn byte_size(&self, major: MatrixMajor) -> u64 { + pub const fn byte_size(&self, repr: Repr, major: MatrixMajor) -> u64 { let (vec, array_len) = self.as_vector_array(major); - let array_stride = array_stride(vec.align(), vec.byte_size()); + let array_stride = array_stride(vec.align(repr), vec.byte_size()); array_size(array_stride, array_len) } pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { let (vec, _) = self.as_vector_array(major); - array_align(vec.align(), repr) + array_align(vec.align(repr), repr) } const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { @@ -266,7 +258,12 @@ impl Matrix { #[allow(missing_docs)] impl Atomic { pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } - pub const fn align(&self) -> U32PowerOf2 { self.scalar.as_scalar_type().align() } + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + self.scalar.as_scalar_type().align() + } } #[allow(missing_docs)] @@ -290,8 +287,9 @@ pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_strid pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage | Repr::Packed => element_align, + Repr::Storage => element_align, Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), + Repr::Packed => PACKED_ALIGN, } } @@ -312,12 +310,12 @@ impl RuntimeSizedArray { #[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } } #[allow(missing_docs)] impl RuntimeSizedArrayField { - pub fn byte_align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } } pub const fn round_up(multiple_of: u64, n: u64) -> u64 { diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index d46fce2..4e95718 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -18,7 +18,7 @@ use crate::{ Len, }, }; -use layoutable::{LayoutableType, Matrix, Vector}; +use layoutable::{align_size::PACKED_ALIGN, LayoutableType, Matrix, Vector}; pub(crate) mod builder; pub(crate) mod eq; @@ -144,6 +144,15 @@ pub mod repr { Packed, } + impl Repr { + /// True if `Repr::Storage` + pub const fn is_storage(self) -> bool { matches!(self, Repr::Storage) } + /// True if `Repr::Uniform` + pub const fn is_uniform(self) -> bool { matches!(self, Repr::Uniform) } + /// True if `Repr::Packed` + pub const fn is_packed(self) -> bool { matches!(self, Repr::Packed) } + } + macro_rules! type_repr { ($($constraint:ident),*) => { $( @@ -215,65 +224,56 @@ pub(in super::super::rust_types) mod type_layout_internal { pub struct LayoutCalculator { next_offset_min: u64, align: U32PowerOf2, - packed: bool, + repr: Repr, } impl LayoutCalculator { /// Creates a new `LayoutCalculator`, which calculates the size, align and /// the field offsets of a gpu struct. - pub const fn new(packed: bool) -> Self { + pub const fn new(repr: Repr) -> Self { Self { next_offset_min: 0, align: U32PowerOf2::_1, - packed, + repr, } } - /// Extends the layout by a field given it's size and align. + /// Extends the layout by a field. + /// + /// `is_struct` must be true if the field is a struct. /// /// Returns the field's offset. pub const fn extend( &mut self, field_size: u64, - field_align: U32PowerOf2, + mut field_align: U32PowerOf2, custom_min_size: Option, custom_min_align: Option, + is_struct: bool, ) -> u64 { - let size = FieldLayout::calculate_byte_size(field_size, custom_min_size); - let align = FieldLayout::calculate_align(field_align, custom_min_align); + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let size = Self::calculate_byte_size(field_size, custom_min_size); + let align = Self::calculate_align(field_align, custom_min_align); let offset = self.next_field_offset(align, custom_min_align); - self.next_offset_min = offset + size; + self.next_offset_min = match (self.repr, is_struct) { + // The uniform address space requires that: + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + (Repr::Uniform, true) => round_up(16, offset + size), + _ => offset + size, + }; self.align = self.align.max(align); offset } - /// Extends the layout by a field given it's size and align. If the field - /// is unsized, pass `None` as it's size. - /// - /// Returns (byte size, byte align, last field offset). - /// - /// `self` is consumed, so that no further fields may be extended, because - /// only the last field may be unsized. - pub const fn extend_maybe_unsized( - mut self, - field_size: Option, - field_align: U32PowerOf2, - custom_min_size: Option, - custom_min_align: Option, - ) -> (Option, U32PowerOf2, u64) { - if let Some(size) = field_size { - let offset = self.extend(size, field_align, custom_min_size, custom_min_align); - (Some(self.byte_size()), self.align(), offset) - } else { - let (offset, align) = self.extend_unsized(field_align, custom_min_align); - (None, align, offset) - } - } - - - /// Extends the layout by an unsized field given it's align. + /// Extends the layout by an runtime sized array field given it's align. /// /// Returns (last field offset, align) /// @@ -281,10 +281,15 @@ impl LayoutCalculator { /// only the last field may be unsized. pub const fn extend_unsized( mut self, - field_align: U32PowerOf2, + mut field_align: U32PowerOf2, custom_min_align: Option, ) -> (u64, U32PowerOf2) { - let align = FieldLayout::calculate_align(field_align, custom_min_align); + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let align = Self::calculate_align(field_align, custom_min_align); let offset = self.next_field_offset(align, custom_min_align); self.align = self.align.max(align); @@ -306,10 +311,29 @@ impl LayoutCalculator { /// field_align should already respect field_custom_min_align. /// field_custom_min_align is used to overwrite packing if self is packed. const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { - match (self.packed, field_custom_min_align) { - (true, None) => self.next_offset_min, - (true, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), - (false, _) => round_up(field_align.as_u64(), self.next_offset_min), + match (self.repr, field_custom_min_align) { + (Repr::Packed, None) => self.next_offset_min, + (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (_, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } + + const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align } } } @@ -499,30 +523,11 @@ impl FieldLayout { fn byte_size(&self) -> Option { self.ty .byte_size() - .map(|byte_size| Self::calculate_byte_size(byte_size, self.custom_min_size.0)) + .map(|byte_size| LayoutCalculator::calculate_byte_size(byte_size, self.custom_min_size.0)) } /// The alignment of the field with `custom_min_align` taken into account. - fn byte_align(&self) -> U32PowerOf2 { Self::calculate_align(self.ty.align(), self.custom_min_align.0) } - - const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { - // const byte_size.max(custom_min_size.unwrap_or(0)) - if let Some(min_size) = custom_min_size { - if min_size > byte_size { - return min_size; - } - } - byte_size - } - - const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) - if let Some(min_align) = custom_min_align { - align.max(min_align) - } else { - align - } - } + fn align(&self) -> U32PowerOf2 { LayoutCalculator::calculate_align(self.ty.align(), self.custom_min_align.0) } } /// Options for the field of a struct. diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index e5ded39..9783eaf 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -3,6 +3,7 @@ use std::{fmt::Display, num::NonZeroU32}; use crate::{ any::U32PowerOf2, common::floating_point::{f16, f32_eq_where_nans_are_equal, f64_eq_where_nans_are_equal}, + frontend::rust_types::type_layout::{self, layoutable::align_size::PACKED_ALIGN}, ir::Comp4, }; @@ -529,7 +530,11 @@ impl PackedVector { } } - pub fn align(&self) -> U32PowerOf2 { + pub fn align(&self, repr: type_layout::Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + let align = match self.byte_size() { PackedVectorByteSize::_2 => SizedType::Vector(Len::X1, ScalarType::F16).align(), PackedVectorByteSize::_4 => SizedType::Vector(Len::X1, ScalarType::U32).align(), From 1d3aadc386db15595cf2245249f4d8b4c298d7e3 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 24 May 2025 20:07:41 +0200 Subject: [PATCH 019/182] Fix shame::Struct layout trait impls --- shame/src/frontend/rust_types/struct_.rs | 16 ++++++---------- .../type_layout/layoutable/align_size.rs | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index 14e4c6e..aaa82aa 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -135,19 +135,15 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl LayoutableSized for Struct { - fn layoutable_type_sized() -> layoutable::SizedType { - layoutable::SizedStruct::try_from(T::get_sizedstruct_type()) - .expect("no bools") - .into() - } +impl LayoutableSized for Struct { + fn layoutable_type_sized() -> layoutable::SizedType { T::layoutable_type_sized() } } -impl Layoutable for Struct { - fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } +impl Layoutable for Struct { + fn layoutable_type() -> layoutable::LayoutableType { T::layoutable_type() } } -impl GpuLayout for Struct { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } +impl GpuLayout for Struct { + fn gpu_repr() -> type_layout::Repr { T::gpu_repr() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index e080a97..21204a8 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -92,8 +92,8 @@ impl SizedStruct { /// This is an expensive operation as it calculates byte size and align from scratch. /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. - pub fn byte_size_and_align(&self, layout: Repr) -> (u64, U32PowerOf2) { - let mut field_offsets = self.field_offsets(layout); + pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + let mut field_offsets = self.field_offsets(repr); (&mut field_offsets).count(); // &mut so it doesn't consume (field_offsets.byte_size(), field_offsets.align()) } @@ -288,7 +288,7 @@ pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 match repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage => element_align, - Repr::Uniform => U32PowerOf2::try_from_u32(round_up(16, element_align.as_u64()) as u32).unwrap(), + Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), Repr::Packed => PACKED_ALIGN, } } From ea6f659ef5ef5ce14366421241bed5a14e8ac0bd Mon Sep 17 00:00:00 2001 From: chronicl Date: Sun, 25 May 2025 01:17:19 +0200 Subject: [PATCH 020/182] Move LayoutCalculator --- .../type_layout/layoutable/align_size.rs | 125 ++++++++++++++++- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 128 +----------------- shame/src/lib.rs | 2 +- 4 files changed, 130 insertions(+), 127 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 21204a8..5213c05 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -1,4 +1,4 @@ -use super::super::{LayoutCalculator, Repr}; +use super::super::{Repr}; use super::*; // Size and align of layoutable types // @@ -336,3 +336,126 @@ pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32Powe // In both cases rounded_up is a power of 2 U32PowerOf2::try_from_u32(rounded_up as u32).unwrap() } + +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. +/// +/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s +/// are ignored and the field is inserted directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. +#[derive(Debug, Clone)] +pub struct LayoutCalculator { + next_offset_min: u64, + align: U32PowerOf2, + repr: Repr, +} + +impl LayoutCalculator { + /// Creates a new `LayoutCalculator`, which calculates the size, align and + /// the field offsets of a gpu struct. + pub const fn new(repr: Repr) -> Self { + Self { + next_offset_min: 0, + align: U32PowerOf2::_1, + repr, + } + } + + /// Extends the layout by a field. + /// + /// `is_struct` must be true if the field is a struct. + /// + /// Returns the field's offset. + pub const fn extend( + &mut self, + field_size: u64, + mut field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + is_struct: bool, + ) -> u64 { + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let size = Self::calculate_byte_size(field_size, custom_min_size); + let align = Self::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.next_offset_min = match (self.repr, is_struct) { + // The uniform address space requires that: + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + (Repr::Uniform, true) => round_up(16, offset + size), + _ => offset + size, + }; + self.align = self.align.max(align); + + offset + } + + /// Extends the layout by an runtime sized array field given it's align. + /// + /// Returns (last field offset, align) + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_unsized( + mut self, + mut field_align: U32PowerOf2, + custom_min_align: Option, + ) -> (u64, U32PowerOf2) { + // Just in case the user didn't already do this. + if self.repr.is_packed() { + field_align = PACKED_ALIGN; + } + + let align = Self::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.align = self.align.max(align); + + (offset, self.align) + } + + /// Returns the byte size of the struct. + // wgsl spec: + // roundUp(AlignOf(S), justPastLastMember) + // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) + // + // self.next_offset_min is justPastLastMember already. + pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } + + /// Returns the align of the struct. + pub const fn align(&self) -> U32PowerOf2 { self.align } + + /// field_align should already respect field_custom_min_align. + /// field_custom_min_align is used to overwrite packing if self is packed. + const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { + match (self.repr, field_custom_min_align) { + (Repr::Packed, None) => self.next_offset_min, + (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (_, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } + + pub(crate) const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + pub(crate) const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align + } + } +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 8016763..16a2c93 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -16,7 +16,7 @@ pub(crate) mod align_size; pub(crate) mod builder; pub(crate) mod ir_compat; -pub use align_size::{FieldOffsets, MatrixMajor, array_size, array_stride, array_align}; +pub use align_size::{FieldOffsets, MatrixMajor, LayoutCalculator, array_size, array_stride, array_align}; pub use builder::SizedOrArray; /// Types that have a defined memory layout. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 4e95718..4a1a520 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -18,7 +18,10 @@ use crate::{ Len, }, }; -use layoutable::{align_size::PACKED_ALIGN, LayoutableType, Matrix, Vector}; +use layoutable::{ + align_size::{LayoutCalculator, PACKED_ALIGN}, + LayoutableType, Matrix, Vector, +}; pub(crate) mod builder; pub(crate) mod eq; @@ -215,129 +218,6 @@ pub(in super::super::rust_types) mod type_layout_internal { } } -/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. -/// -/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s -/// are ignored and the field is inserted directly after the previous field. However, -/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. -#[derive(Debug, Clone)] -pub struct LayoutCalculator { - next_offset_min: u64, - align: U32PowerOf2, - repr: Repr, -} - -impl LayoutCalculator { - /// Creates a new `LayoutCalculator`, which calculates the size, align and - /// the field offsets of a gpu struct. - pub const fn new(repr: Repr) -> Self { - Self { - next_offset_min: 0, - align: U32PowerOf2::_1, - repr, - } - } - - /// Extends the layout by a field. - /// - /// `is_struct` must be true if the field is a struct. - /// - /// Returns the field's offset. - pub const fn extend( - &mut self, - field_size: u64, - mut field_align: U32PowerOf2, - custom_min_size: Option, - custom_min_align: Option, - is_struct: bool, - ) -> u64 { - // Just in case the user didn't already do this. - if self.repr.is_packed() { - field_align = PACKED_ALIGN; - } - - let size = Self::calculate_byte_size(field_size, custom_min_size); - let align = Self::calculate_align(field_align, custom_min_align); - - let offset = self.next_field_offset(align, custom_min_align); - self.next_offset_min = match (self.repr, is_struct) { - // The uniform address space requires that: - // - If a structure member itself has a structure type S, then the number of - // bytes between the start of that member and the start of any following - // member must be at least roundUp(16, SizeOf(S)). - (Repr::Uniform, true) => round_up(16, offset + size), - _ => offset + size, - }; - self.align = self.align.max(align); - - offset - } - - /// Extends the layout by an runtime sized array field given it's align. - /// - /// Returns (last field offset, align) - /// - /// `self` is consumed, so that no further fields may be extended, because - /// only the last field may be unsized. - pub const fn extend_unsized( - mut self, - mut field_align: U32PowerOf2, - custom_min_align: Option, - ) -> (u64, U32PowerOf2) { - // Just in case the user didn't already do this. - if self.repr.is_packed() { - field_align = PACKED_ALIGN; - } - - let align = Self::calculate_align(field_align, custom_min_align); - - let offset = self.next_field_offset(align, custom_min_align); - self.align = self.align.max(align); - - (offset, self.align) - } - - /// Returns the byte size of the struct. - // wgsl spec: - // roundUp(AlignOf(S), justPastLastMember) - // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) - // - // self.next_offset_min is justPastLastMember already. - pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } - - /// Returns the align of the struct. - pub const fn align(&self) -> U32PowerOf2 { self.align } - - /// field_align should already respect field_custom_min_align. - /// field_custom_min_align is used to overwrite packing if self is packed. - const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { - match (self.repr, field_custom_min_align) { - (Repr::Packed, None) => self.next_offset_min, - (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), - (_, _) => round_up(field_align.as_u64(), self.next_offset_min), - } - } - - const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { - // const byte_size.max(custom_min_size.unwrap_or(0)) - if let Some(min_size) = custom_min_size { - if min_size > byte_size { - return min_size; - } - } - byte_size - } - - const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) - if let Some(min_align) = custom_min_align { - align.max(min_align) - } else { - align - } - } -} - /// a sized or unsized struct type with 0 or more fields #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StructLayout { diff --git a/shame/src/lib.rs b/shame/src/lib.rs index ed8770f..b5dac06 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -508,7 +508,7 @@ pub mod any { pub use type_layout::layoutable::CanonName; pub use type_layout::layoutable::SizedOrArray; // layout calculation utility - pub use type_layout::LayoutCalculator; + pub use type_layout::layoutable::LayoutCalculator; pub use type_layout::layoutable::array_stride; pub use type_layout::layoutable::array_size; pub use type_layout::layoutable::array_align; From c4456f2c30a90bed85d6e011dbd0288fa12cee87 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 06:56:17 +0200 Subject: [PATCH 021/182] Split TypeLayout into TypeLayout and GpuTypeLayout --- examples/type_layout/src/main.rs | 109 +--- shame/src/common/proc_macro_reexports.rs | 1 + shame/src/common/proc_macro_utils.rs | 46 +- shame/src/common/small_vec_actual.rs | 17 + shame/src/frontend/encoding/io_iter.rs | 2 +- shame/src/frontend/rust_types/array.rs | 5 +- shame/src/frontend/rust_types/atomic.rs | 4 +- .../src/frontend/rust_types/layout_traits.rs | 25 +- shame/src/frontend/rust_types/mat.rs | 4 +- shame/src/frontend/rust_types/packed_vec.rs | 6 +- shame/src/frontend/rust_types/struct_.rs | 4 +- .../rust_types/type_layout/builder.rs | 494 ---------------- .../rust_types/type_layout/construction.rs | 555 ++++++++++++++++++ .../src/frontend/rust_types/type_layout/eq.rs | 16 +- .../type_layout/layoutable/align_size.rs | 27 +- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 136 ++--- shame/src/frontend/rust_types/type_traits.rs | 2 +- shame/src/frontend/rust_types/vec.rs | 3 +- shame/src/ir/pipeline/wip_pipeline.rs | 3 +- shame/src/lib.rs | 2 +- shame_derive/src/derive_layout.rs | 9 +- shame_derive/src/util.rs | 4 - 23 files changed, 728 insertions(+), 748 deletions(-) delete mode 100644 shame/src/frontend/rust_types/type_layout/builder.rs create mode 100644 shame/src/frontend/rust_types/type_layout/construction.rs diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 11b99aa..35830bc 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -7,7 +7,7 @@ use shame::{ self, layout::{ self, FieldOptions, LayoutableSized, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, - UnsizedStruct, Vector, + UnsizedStruct, Vector, GpuTypeLayout, }, U32PowerOf2, }, @@ -16,7 +16,7 @@ use shame::{ }; fn main() { - // We'll start by building a `TypeLayout`, which for this Vertex type. + // We'll start by replicating this struct using `any::layout` types. #[derive(GpuLayout)] struct Vertex { position: f32x3, @@ -29,105 +29,28 @@ fn main() { let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) .extend("normal", f32x3::layoutable_type_sized()) .extend("uv", f32x1::layoutable_type_sized()); - // A layout that follows the storage layout rules - let layout: TypeLayout = TypeLayout::new_layout_for(sized_struct.clone(), Repr::Storage); - // Or we can get a TypeLayout, which guarantees the storage layout rules. - let layout_storage: TypeLayout = TypeLayout::new_storage_layout_for(sized_struct.clone()); - assert_eq!(layout, layout_storage); - // Or we get it as a packed layout - let layout_packed: TypeLayout = TypeLayout::new_packed_layout_for(sized_struct); - assert_ne!(layout_storage, layout_packed); + let storage = GpuTypeLayout::::new(sized_struct.clone()); + let packed = GpuTypeLayout::::new(sized_struct); + assert_ne!(storage.layout(), packed.layout()); - // Now we'll replicate the layout of this struct - #[derive(GpuLayout)] - struct A { - a: f32x4, - b: f32x3, - c: Array, - } - - // By default structs are #[gpu_repr(Storage)], which means that it follows - // the wgsl storage layout rules (std430). To obtain a corresponding TypeLayout - // we first need to build a `CpuShareableType`, in our case an `UnsizedStruct`. - let unsized_struct = UnsizedStruct { - name: "A".into(), - sized_fields: vec![ - SizedField::new("a", Vector::new(ScalarType::F32, Len::X4)), - SizedField::new("b", f32x3::layoutable_type_sized()), - ], - last_unsized: RuntimeSizedArrayField::new("c", None, f32x1::layoutable_type_sized()), - }; - // And now we can get the `TypeLayout`. - let s_layout = TypeLayout::new_storage_layout_for(unsized_struct.clone()); - // For now `TypeLayout::::new_layout_for` only accepts sized types, - // however `TypeLayout::::new_layout_for_unchecked` allows to obtain the - // the uniform layout of an unsized cpu-shareable. Using that layout with wgsl as your - // target language will cause an error. - let u_layout = TypeLayout::new_uniform_layout_for(unsized_struct); - // This struct's field offsets are different for storage and uniform layout rules. The array - // has an alignment of 4 with storage alignment and an alignment of 16 with uniform alignment. - assert_ne!(s_layout, u_layout); - - #[derive(GpuLayout)] - struct B { - a: f32x4, - b: f32x3, - c: f32x1, - } + // Does not exist: + // let uniform = GpuTypeLayout::::new(sized_struct.clone()); - // Sized structs require a builder to ensure it always contains at least one field. - let mut sized_struct = SizedStruct::new("B", "b", f32x4::layoutable_type_sized()) - .extend("b", f32x3::layoutable_type_sized()) - .extend("c", f32x1::layoutable_type_sized()); - // Since this struct is sized we can use TypeLayout::::new_layout_for. - let u_layout = TypeLayout::new_uniform_layout_for(sized_struct.clone()); - let s_layout = TypeLayout::new_storage_layout_for(sized_struct); - // And this time they are equal, despite different layout rules. - assert_eq!(s_layout, u_layout); - // Using `TryFrom::try_from` we can check whether the storage type layout also follows - // uniform layout rules despite not being `TypeLayout`, - // which in this case will succeed, but if it doesn't we get a very nice error message about - // why the layout is not compatible with the uniform layout rules. - let u_layout = TypeLayout::::try_from(&s_layout).unwrap(); + // However we can try to upgrade a GpuTypeLayout:: + let uniform = GpuTypeLayout::::try_from(storage.clone()).unwrap(); - // Let's replicate a more complex example with explicit field size and align. - #[derive(shame::GpuLayout)] - struct C { - a: f32x2, - #[size(16)] - b: f32x1, - #[align(16)] - c: f32x2, - } - - let mut sized_struct = SizedStruct::new("C", "a", f32x3::layoutable_type_sized()) - .extend(FieldOptions::new("b", None, Some(16)), f32x3::layoutable_type_sized()) - .extend( - FieldOptions::new("c", Some(U32PowerOf2::_16), None), - f32x1::layoutable_type_sized(), - ); - let layout = TypeLayout::new_storage_layout_for(sized_struct); - assert!(layout.align.as_u32() == 16); - - // gpu_repr(uniform) and gpu_repr(storage), which is the default, are now supported - #[derive(shame::GpuLayout)] - #[gpu_repr(uniform)] - struct D { - a: f32x2, - b: Array, - } - - // this would be 8 for storage layout rules - assert!(gpu_layout::().align.as_u32() == 16); + // Which if it succeeds, guarantees: + assert_eq!(storage.layout(), uniform.layout()); - // Let's end on a pretty error message + // // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) // This has align of 4 for storage and align of 16 for uniform. .extend("b", Array::>::layoutable_type_sized()); - let s_layout = TypeLayout::new_storage_layout_for(sized_struct); - let result = TypeLayout::::try_from(&s_layout); - match result { + + let storage = GpuTypeLayout::::new(sized_struct.clone()); + let uniform_result = GpuTypeLayout::::try_from(storage.clone()); + match uniform_result { Err(e) => println!("This error is a showcase:\n{}", e), Ok(u_layout) => println!("It unexpectedly worked, ohh no."), } diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index b78acee..e467fd8 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -28,6 +28,7 @@ pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; pub use crate::frontend::rust_types::type_layout::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; pub use crate::frontend::rust_types::type_layout::Repr; +pub use crate::frontend::rust_types::type_layout::repr; pub use crate::frontend::rust_types::type_layout::TypeLayout; pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; pub use crate::frontend::rust_types::type_traits::BindingArgs; diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 85f0d19..164aecb 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -54,6 +54,7 @@ pub fn collect_into_array_exact(mut it: impl Iterator, struct_name: &'static str, first_fields_with_offsets_and_sizes: &[(ReprCField, usize, usize)], - last_field: ReprCField, + mut last_field: ReprCField, last_field_size: Option, ) -> Result { let last_field_offset = match first_fields_with_offsets_and_sizes.last() { @@ -99,29 +100,31 @@ pub fn repr_c_struct_layout( let total_struct_size = last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); + let new_size = |layout_size: Option, actual_size: Option| { + (layout_size != actual_size).then_some(actual_size).flatten() + }; let mut fields = first_fields_with_offsets_and_sizes .iter() - .map(|(field, offset, size)| (field, *offset as u64, *size as u64)) - .map(|(field, offset, size)| FieldLayoutWithOffset { - field: FieldLayout { - custom_min_align: None.into(), - custom_min_size: (field.layout.byte_size() != Some(size)).then_some(size).into(), - name: field.name.into(), - ty: field.layout.clone(), - }, - rel_byte_offset: offset, + .map(|(field, offset, size)| (field.clone(), *offset as u64, *size as u64)) + .map(|(mut field, offset, size)| { + field.layout.byte_size = new_size(field.layout.byte_size(), Some(size)); + FieldLayoutWithOffset { + field: FieldLayout { + name: field.name.into(), + ty: field.layout.clone(), + }, + rel_byte_offset: offset, + } }) - .chain(std::iter::once(FieldLayoutWithOffset { - field: FieldLayout { - custom_min_align: None.into(), - custom_min_size: (last_field.layout.byte_size() != last_field_size) - .then_some(last_field_size) - .flatten() - .into(), - name: last_field.name.into(), - ty: last_field.layout, - }, - rel_byte_offset: last_field_offset, + .chain(std::iter::once({ + last_field.layout.byte_size = new_size(last_field.layout.byte_size(), last_field_size); + FieldLayoutWithOffset { + field: FieldLayout { + name: last_field.name.into(), + ty: last_field.layout, + }, + rel_byte_offset: last_field_offset, + } })) .collect::>(); @@ -132,7 +135,6 @@ pub fn repr_c_struct_layout( name: struct_name.into(), fields, })), - None, )) } diff --git a/shame/src/common/small_vec_actual.rs b/shame/src/common/small_vec_actual.rs index 939cbb9..ccdca81 100644 --- a/shame/src/common/small_vec_actual.rs +++ b/shame/src/common/small_vec_actual.rs @@ -222,6 +222,23 @@ impl std::borrow::Borrow<[T]> for SmallVec { fn borrow(&self) -> &[T] { self } } +impl std::fmt::Display for SmallVec +where + T: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + let mut iter = self.iter(); + if let Some(first) = iter.next() { + write!(f, "{}", first)?; + for item in iter { + write!(f, ", {}", item)?; + } + } + write!(f, "]") + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index e3e20af..b31c427 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -107,7 +107,7 @@ impl VertexBuffer<'_, T> { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check).into_plain(); + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index cf49295..8899dc4 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,7 +5,7 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{self, layoutable, ElementLayout, TypeLayout, TypeLayoutSemantics}; +use super::type_layout::{self, layoutable, repr, ElementLayout, TypeLayout, TypeLayoutSemantics}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; @@ -179,7 +179,7 @@ impl ToGpuType for Array { } impl GpuLayout for Array { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -207,7 +207,6 @@ impl GpuLayout }), N::LEN.map(NonZeroU32::get), ), - None, ), ); diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index 0a6ff26..0e461c7 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -9,7 +9,7 @@ use super::{ type_layout::{ self, layoutable::{self, LayoutableSized}, - TypeLayout, + repr, TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, @@ -146,7 +146,7 @@ impl Layoutable for Atomic { } impl GpuLayout for Atomic { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index bb2bdff..6bcc937 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -22,10 +22,11 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::repr::TypeRepr; +use super::type_layout::repr::{TypeRepr, TypeReprStorageOrPacked}; use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ - self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics, + self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, GpuTypeLayout, StructLayout, TypeLayout, + TypeLayoutSemantics, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -139,7 +140,8 @@ use std::rc::Rc; /// pub trait GpuLayout: Layoutable { /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. - fn gpu_repr() -> Repr; + // fn gpu_repr() -> Repr; + type GpuRepr: TypeReprStorageOrPacked; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a /// corresponding Cpu type to the Gpu type that the derive macro is used on. @@ -175,7 +177,11 @@ pub trait GpuLayout: Layoutable { /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` pub fn gpu_layout() -> TypeLayout { - TypeLayout::new_layout_for(T::layoutable_type(), T::gpu_repr()) + TypeLayout::new_layout_for(&T::layoutable_type(), ::REPR) +} + +pub fn gpu_type_layout() -> GpuTypeLayout { + GpuTypeLayout::new(T::layoutable_type()) } /// (no documentation yet) @@ -214,11 +220,11 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, - cpu_layout: &TypeLayout, - gpu_layout: &TypeLayout, + cpu_layout: &TypeLayout, + gpu_layout: &TypeLayout, skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { @@ -554,7 +560,8 @@ impl Layoutable for GpuT { } impl GpuLayout for GpuT { - fn gpu_repr() -> Repr { todo!() } + // fn gpu_repr() -> Repr { todo!() } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -775,7 +782,6 @@ impl CpuLayout for [T; N] { }), Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), ), - None, ) } @@ -819,7 +825,6 @@ impl CpuLayout for [T] { }), None, ), - None, ) } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 4795935..07d3404 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -10,7 +10,7 @@ use super::{ type_layout::{ self, layoutable::{self, LayoutableSized}, - TypeLayout, + repr, TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, @@ -69,7 +69,7 @@ impl Layoutable for mat { } impl GpuLayout for mat { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 596a0bb..88c536e 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -25,7 +25,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{self, layoutable, type_layout_internal, TypeLayout, TypeLayoutSemantics}, + type_layout::{self, layoutable, repr, Repr, TypeLayout, TypeLayoutSemantics}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -148,13 +148,13 @@ impl Layoutable for PackedVec { } impl GpuLayout for PackedVec { - fn gpu_repr() -> type_layout::Repr { type_layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty = Self::sized_ty_equivalent(); let name = sized_ty.to_string().into(); let sized_ty: layoutable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); - let layout = TypeLayout::new_storage_layout_for(sized_ty); + let layout = TypeLayout::new_layout_for(&sized_ty.into(), Repr::Storage); Some(Ok((name, layout.into()))) } } diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index aaa82aa..f50c185 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -23,7 +23,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::{self, layoutable, TypeLayout}; +use super::type_layout::{self, layoutable, repr, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -143,7 +143,7 @@ impl Layoutable for Struct } impl GpuLayout for Struct { - fn gpu_repr() -> type_layout::Repr { T::gpu_repr() } + type GpuRepr = T::GpuRepr; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/builder.rs b/shame/src/frontend/rust_types/type_layout/builder.rs deleted file mode 100644 index fdaaaa7..0000000 --- a/shame/src/frontend/rust_types/type_layout/builder.rs +++ /dev/null @@ -1,494 +0,0 @@ -use crate::ir::{ir_type::max_u64_po2_dividing, StoreType}; -use super::{ - layoutable::{ - MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, UnsizedStruct, - }, - *, -}; -use TypeLayoutSemantics as TLS; - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to wgsl storage layout rules (std430). - pub fn new_storage_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Storage)) - } - - /// Get the `LayoutableType` this layout is based on. - pub fn layoutable_type(&self) -> &LayoutableType { - self.layoutable_type - .as_ref() - .expect("Storage is always based on a layoutable type") - } -} - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to wgsl uniform layout rules (std140). - pub fn new_uniform_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Uniform)) - } - - /// Get the `LayoutableType` this layout is based on. - pub fn layoutable_type(&self) -> &LayoutableType { - self.layoutable_type - .as_ref() - .expect("Uniform is always based on a layoutable type") - } -} - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` in packed format. - pub fn new_packed_layout_for(ty: impl Into) -> Self { - type_layout_internal::cast_unchecked(TypeLayout::new_layout_for(ty, Repr::Packed)) - } - - /// Get the `LayoutableType` this layout is based on. - pub fn layoutable_type(&self) -> &LayoutableType { - self.layoutable_type - .as_ref() - .expect("Packed is always based on a layoutable type") - } -} - -impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to the given `repr`. - pub fn new_layout_for(ty: impl Into, repr: Repr) -> Self { - match ty.into() { - LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), - LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), - LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), - } - } - - fn from_sized_type(ty: SizedType, repr: Repr) -> Self { - let (size, align, tls) = match &ty { - SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), - SizedType::Atomic(a) => ( - a.byte_size(), - a.align(repr), - TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), - ), - SizedType::Matrix(m) => ( - m.byte_size(repr, MatrixMajor::Row), - m.align(repr, MatrixMajor::Row), - TLS::Matrix(*m), - ), - SizedType::Array(a) => ( - a.byte_size(repr), - a.align(repr), - TLS::Array( - Rc::new(ElementLayout { - byte_stride: a.byte_stride(repr), - ty: Self::from_sized_type((*a.element).clone(), repr), - }), - Some(a.len.get()), - ), - ), - SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), - SizedType::Struct(s) => { - let mut field_offsets = s.field_offsets(repr); - let fields = (&mut field_offsets) - .zip(s.fields()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) - .collect(); - - ( - field_offsets.byte_size(), - field_offsets.align(), - TLS::Structure(Rc::new(StructLayout { - name: s.name.clone().into(), - fields, - })), - ) - } - }; - - TypeLayout::new(Some(size), align, tls, Some(ty.into())) - } - - fn from_unsized_struct(s: UnsizedStruct, repr: Repr) -> Self { - let mut field_offsets = s.sized_field_offsets(repr); - let mut fields = (&mut field_offsets) - .zip(s.sized_fields.iter()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) - .collect::>(); - - let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); - fields.push(FieldLayoutWithOffset { - rel_byte_offset: field_offset, - field: FieldLayout { - name: s.last_unsized.name.clone(), - custom_min_size: None.into(), - custom_min_align: s.last_unsized.custom_min_align.into(), - ty: Self::from_runtime_sized_array(s.last_unsized.array.clone(), repr), - }, - }); - - TypeLayout::new( - None, - align, - TLS::Structure(Rc::new(StructLayout { - name: s.name.clone().into(), - fields, - })), - Some(s.into()), - ) - } - - fn from_runtime_sized_array(ty: RuntimeSizedArray, repr: Repr) -> Self { - Self::new( - None, - ty.align(repr), - TLS::Array( - Rc::new(ElementLayout { - byte_stride: ty.byte_stride(repr), - ty: Self::from_sized_type(ty.element.clone(), repr), - }), - None, - ), - Some(ty.into()), - ) - } -} - -fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { - FieldLayoutWithOffset { - rel_byte_offset: offset, - field: FieldLayout { - name: field.name.clone(), - custom_min_size: field.custom_min_size.into(), - custom_min_align: field.custom_min_align.into(), - ty: TypeLayout::from_sized_type(field.ty.clone(), repr), - }, - } -} - -impl<'a> TryFrom<&'a TypeLayout> for TypeLayout { - type Error = UniformLayoutError; - - fn try_from(s_layout: &'a TypeLayout) -> Result { - let layoutable_type = s_layout.layoutable_type().clone(); - - // Checking whether it's sized - let sized = match layoutable_type { - LayoutableType::Sized(sized) => sized, - _ => { - return Err(UniformLayoutError::MustBeSized("wgsl", layoutable_type)); - } - }; - - let u_layout = TypeLayout::new_uniform_layout_for(sized); - - // Checking fields - fn check_layout( - s_layout: &TypeLayout, - u_layout: &TypeLayout, - is_top_level: bool, - ) -> Result<(), Mismatch> { - // kinds are the same, because the type layouts are based on the same LayoutableType - match (&s_layout.kind, &u_layout.kind) { - (TLS::Structure(s), TLS::Structure(u)) => { - for (i, (s_field, u_field)) in s.fields.iter().zip(u.fields.iter()).enumerate() { - // Checking field offset - if s_field.rel_byte_offset != u_field.rel_byte_offset { - let struct_type = match s_layout.get_layoutable_type().unwrap() { - LayoutableType::Sized(SizedType::Struct(s)) => StructKind::Sized(s.clone()), - LayoutableType::UnsizedStruct(s) => StructKind::Unsized(s.clone()), - _ => unreachable!("is struct, because tls is struct"), - }; - - return Err(Mismatch::StructureFieldOffset(StructFieldOffsetError { - struct_layout: (**s).clone(), - struct_type, - field_name: s_field.field.name.clone(), - field_index: i, - actual_offset: s_field.rel_byte_offset, - expected_alignment: u_field.field.align(), - is_top_level, - })); - } - - check_layout(&s_field.field.ty, &u_field.field.ty, false)?; - } - } - (TLS::Array(s_ele, _), TLS::Array(u_ele, _)) => { - // As long as the strides are the same, the size must be the same and - // the element align must divide the stride (uniform requirement), because - // u_layout is a valid uniform layout. - if s_ele.byte_stride != u_ele.byte_stride { - let element_ty = match u_ele.ty.get_layoutable_type().unwrap() { - LayoutableType::Sized(s) => s.clone(), - _ => { - unreachable!("elements of an array are always sized for TypeLayout") - } - }; - return Err(Mismatch::ArrayStride(ArrayStrideError { - expected: u_ele.byte_stride, - actual: s_ele.byte_stride, - element_ty, - })); - } - - check_layout(&s_ele.ty, &u_ele.ty, false)?; - } - _ => {} - } - - Ok(()) - } - - - match check_layout(s_layout, &u_layout, true) { - Ok(()) => Ok(u_layout), - Err(e) => { - let ctx = LayoutErrorContext { - s_layout: s_layout.clone(), - u_layout, - // TODO(chronicl) default shouldn't be true? - use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(false), - }; - - use UniformLayoutError as U; - let e = match e { - Mismatch::ArrayStride(e) => U::ArrayStride(WithContext::new(ctx, e)), - Mismatch::StructureFieldOffset(e) => U::StructureFieldOffset(WithContext::new(ctx, e)), - }; - Err(e) - } - } - } -} - -/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. -#[derive(thiserror::Error, Debug, Clone)] -pub enum UniformLayoutError { - #[error("{0}")] - ArrayStride(WithContext), - #[error("{0}")] - StructureFieldOffset(WithContext), - #[error( - "The size of `{1}` on the gpu is not known at compile time. `{0}` \ - requires that the size of uniform buffers on the gpu is known at compile time." - )] - MustBeSized(&'static str, LayoutableType), -} - -#[derive(Debug, Clone)] -enum Mismatch { - ArrayStride(ArrayStrideError), - StructureFieldOffset(StructFieldOffsetError), -} - - -#[derive(Debug, Clone)] -pub struct LayoutErrorContext { - s_layout: TypeLayout, - u_layout: TypeLayout, - use_color: bool, -} - -#[derive(Debug, Clone)] -pub struct WithContext { - ctx: LayoutErrorContext, - inner: T, -} - -impl std::ops::Deref for WithContext { - type Target = T; - fn deref(&self) -> &Self::Target { &self.inner } -} - -impl WithContext { - fn new(ctx: LayoutErrorContext, inner: T) -> Self { Self { ctx, inner } } -} - - -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct ArrayStrideError { - expected: u64, - actual: u64, - element_ty: SizedType, -} - -impl std::error::Error for WithContext {} -impl Display for WithContext { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level = self.ctx.s_layout.layoutable_type(); - writeln!( - f, - "array elements within type `{}` do not satisfy uniform layout requirements.", - top_level - )?; - writeln!( - f, - "The array with `{}` elements requires stride {}, but has stride {}.", - self.element_ty, self.expected, self.actual - )?; - writeln!(f, "The full layout of `{}` is:", top_level)?; - self.ctx.s_layout.write("", self.ctx.use_color, f)?; - writeln!(f)?; - writeln!( - f, - "\nfor more information on the layout rules, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - )?; - Ok(()) - } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct StructFieldOffsetError { - pub struct_layout: StructLayout, - pub struct_type: StructKind, - pub field_name: CanonName, - pub field_index: usize, - pub actual_offset: u64, - pub expected_alignment: U32PowerOf2, - pub is_top_level: bool, -} - -#[derive(Debug, Clone)] -pub enum StructKind { - Sized(SizedStruct), - Unsized(UnsizedStruct), -} - -impl std::error::Error for WithContext {} -impl Display for WithContext { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let top_level_type = self.ctx.s_layout.layoutable_type(); - writeln!( - f, - "The type `{top_level_type}` cannot be used as a uniform buffer binding." - )?; - - match self.is_top_level { - true => write!(f, "Struct `{}`", &*self.struct_layout.name)?, - false => write!(f, "It contains a struct `{}`, which", &*self.struct_layout.name)?, - } - writeln!(f, " does not satisfy the uniform memory layout requirements.",)?; - writeln!(f)?; - // TODO(chronicl) structure_def_location - // if let Some(call_info) = structure_def_location { - // writeln!(f, "Definition at {call_info}")?; - // } - - write_struct_layout( - &self.struct_layout, - &self.struct_type, - self.ctx.use_color, - Some(self.field_index), - f, - )?; - - let actual_align = max_u64_po2_dividing(self.actual_offset); - let expected_alignment = self.expected_alignment.as_u32(); - writeln!(f)?; - set_color(f, Some("#508EE3"), false)?; - writeln!( - f, - "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", - self.field_name, expected_alignment, self.actual_offset - )?; - set_color(f, None, false)?; - writeln!(f)?; - - writeln!(f, "Potential solutions include:")?; - writeln!( - f, - "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", - expected_alignment, self.field_name - )?; - writeln!(f, "- use a storage binding instead of a uniform binding")?; - writeln!( - f, - "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", - self.field_name, expected_alignment - )?; - writeln!(f)?; - - writeln!( - f, - "In the uniform address space, structs, arrays and array elements must be at least 16 byte aligned.\nMore info about the uniform address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints" - )?; - Ok(()) - } -} - -/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. -fn write_struct_layout( - struct_layout: &StructLayout, - struct_type: &StructKind, - colored: bool, - highlight_field: Option, - f: &mut F, -) -> std::fmt::Result -where - F: Write, -{ - let use_256_color_mode = false; - let color = |f_: &mut F, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut F| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - let (sized_fields, last_unsized) = match struct_type { - StructKind::Sized(s) => (s.fields(), None), - StructKind::Unsized(s) => (s.sized_fields.as_slice(), Some(&s.last_unsized)), - }; - - let struct_name = &*struct_layout.name; - - let indent = " "; - let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); - let header = format!("struct {} {{", struct_name); - let table_start_column = 1 + sized_fields - .iter() - .map(field_decl_line) - .map(|s| s.len()) - .max() - .unwrap_or(0) - .max(header.chars().count()); - f.write_str(&header)?; - for i in header.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "offset align size")?; - for (i, (field, layout)) in sized_fields.iter().zip(struct_layout.fields.iter()).enumerate() { - if Some(i) == highlight_field { - color(f, "#508EE3")?; - } - let (align, size) = (layout.ty.align(), layout.ty.byte_size().expect("is sized field")); - let decl_line = field_decl_line(field); - f.write_str(&decl_line)?; - // write spaces to table on the right - for _ in decl_line.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "{:6} {:5} {:4}", layout.rel_byte_offset, align.as_u32(), size)?; - if Some(i) == highlight_field { - reset(f)?; - } - } - if let Some(last_field) = last_unsized { - let layout = struct_layout.fields.last().expect("structs have at least one field"); - - let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); - f.write_str(&decl_line)?; - // write spaces to table on the right - for _ in decl_line.len()..table_start_column { - f.write_char(' ')? - } - write!(f, "{:6} {:5}", layout.rel_byte_offset, layout.ty.align().as_u32())?; - } - writeln!(f, "}}")?; - Ok(()) -} diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs new file mode 100644 index 0000000..77c8178 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -0,0 +1,555 @@ +use std::fmt::Formatter; + +use crate::{ + __private::SmallVec, + ir::{ir_type::max_u64_po2_dividing, StoreType}, +}; +use super::{ + layoutable::{ + FieldOffsets, MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, + UnsizedStruct, + }, + *, +}; +use TypeLayoutSemantics as TLS; + +impl TypeLayout { + /// Returns the type layout of the given `LayoutableType` + /// layed out according to the given `repr`. + pub fn new_layout_for(ty: &LayoutableType, repr: Repr) -> Self { + match ty { + LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), + LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), + LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), + } + } + + fn from_sized_type(ty: &SizedType, repr: Repr) -> Self { + let (size, align, tls) = match &ty { + SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), + SizedType::Atomic(a) => ( + a.byte_size(), + a.align(repr), + TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), + ), + SizedType::Matrix(m) => ( + m.byte_size(repr, MatrixMajor::Row), + m.align(repr, MatrixMajor::Row), + TLS::Matrix(*m), + ), + SizedType::Array(a) => ( + a.byte_size(repr), + a.align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: a.byte_stride(repr), + ty: Self::from_sized_type(&a.element, repr), + }), + Some(a.len.get()), + ), + ), + SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), + SizedType::Struct(s) => { + let mut field_offsets = s.field_offsets(repr); + let fields = (&mut field_offsets) + .zip(s.fields()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect(); + + ( + field_offsets.byte_size(), + field_offsets.align(), + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + ) + } + }; + + TypeLayout::new(Some(size), align, tls) + } + + fn from_unsized_struct(s: &UnsizedStruct, repr: Repr) -> Self { + let mut field_offsets = s.sized_field_offsets(repr); + let mut fields = (&mut field_offsets) + .zip(s.sized_fields.iter()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) + .collect::>(); + + let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + fields.push(FieldLayoutWithOffset { + rel_byte_offset: field_offset, + field: FieldLayout { + name: s.last_unsized.name.clone(), + ty: Self::from_runtime_sized_array(&s.last_unsized.array, repr), + }, + }); + + TypeLayout::new( + None, + align, + TLS::Structure(Rc::new(StructLayout { + name: s.name.clone().into(), + fields, + })), + ) + } + + fn from_runtime_sized_array(ty: &RuntimeSizedArray, repr: Repr) -> Self { + Self::new( + None, + ty.align(repr), + TLS::Array( + Rc::new(ElementLayout { + byte_stride: ty.byte_stride(repr), + ty: Self::from_sized_type(&ty.element, repr), + }), + None, + ), + ) + } +} + +fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + FieldLayoutWithOffset { + rel_byte_offset: offset, + field: FieldLayout { + name: field.name.clone(), + ty: TypeLayout::from_sized_type(&field.ty, repr), + }, + } +} + +impl GpuTypeLayout { + /// Creates a new `GpuTypeLayout` where `T` is either `Storage` or `Packed`. + /// + /// All LayoutableType's can be layed out according to storage and packed layout rules, + /// which corresponds to the available #[gpu_repr(packed)] and #[gpu_repr(storage)] + /// attributes when deriving `GpuLayout` for a struct. + /// + /// `GpuTypeLayout` can be acquired via + /// `GpuTypeLayout::::try_from(gpu_type_layout_storage)`. + pub fn new(ty: impl Into) -> Self { + Self { + ty: ty.into(), + _repr: PhantomData, + } + } +} + +impl TryFrom> for GpuTypeLayout { + type Error = LayoutError; + + fn try_from(ty: GpuTypeLayout) -> Result { + check_layout(ty.layoutable_type(), Repr::Storage, Repr::Uniform)?; + Ok(GpuTypeLayout { + ty: ty.ty, + _repr: PhantomData, + }) + } +} + +/// Checks whether the layout of `ty` as `actual_repr` and as `expected_repr` are compatible. +/// Compatible means that all field offsets of structs and all strides of arrays are the same. +/// +/// Another way to say this is, that we are laying `ty` out according to two different +/// layout rules and checking whether the byte representation of those layouts is the same. +fn check_layout(ty: &LayoutableType, actual_repr: Repr, expected_repr: Repr) -> Result<(), LayoutError> { + let ctx = LayoutContext { + top_level_type: ty, + actual_repr, + expected_repr, + use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false), + }; + let is_sized = matches!(ctx.top_level_type, LayoutableType::Sized(_)); + if ctx.expected_repr.is_uniform() && !is_sized { + return Err(LayoutError::UniformBufferMustBeSized( + "wgsl", + ctx.top_level_type.clone(), + )); + } + + match &ctx.top_level_type { + LayoutableType::Sized(s) => check_compare_sized(ctx, s), + LayoutableType::UnsizedStruct(s) => { + let mut actual_offsets = s.sized_field_offsets(ctx.actual_repr); + let mut expected_offsets = s.sized_field_offsets(ctx.expected_repr); + check_sized_fields(ctx, s, &s.sized_fields, &mut actual_offsets, &mut expected_offsets)?; + + let (actual_last_offset, _) = s.last_field_offset_and_struct_align(actual_offsets); + let (expected_last_offset, _) = s.last_field_offset_and_struct_align(expected_offsets); + + if actual_last_offset != expected_last_offset { + let field = &s.last_unsized; + let field_index = s.sized_fields.len(); + return Err(StructFieldOffsetError { + ctx: ctx.into(), + struct_type: s.clone().into(), + field_name: field.name.clone(), + field_index, + actual_offset: actual_last_offset, + expected_alignment: field.align(ctx.expected_repr), + } + .into()); + } + + Ok(()) + } + LayoutableType::RuntimeSizedArray(a) => { + let actual_stride = a.byte_stride(ctx.actual_repr); + let expected_stride = a.byte_stride(ctx.expected_repr); + match actual_stride == expected_stride { + false => Err(ArrayStrideError { + ctx: ctx.into(), + actual_stride, + expected_stride, + element_ty: a.element.clone(), + } + .into()), + true => Ok(()), + } + } + } +} + +fn check_compare_sized(ctx: LayoutContext, ty: &SizedType) -> Result<(), LayoutError> { + match ty { + SizedType::Struct(s) => { + let mut actual_offsets = s.field_offsets(ctx.actual_repr); + let mut expected_offsets = s.field_offsets(ctx.expected_repr); + check_sized_fields(ctx, s, s.fields(), &mut actual_offsets, &mut expected_offsets) + } + SizedType::Array(a) => { + let actual_stride = a.byte_stride(ctx.actual_repr); + let expected_stride = a.byte_stride(ctx.expected_repr); + match actual_stride == expected_stride { + false => Err(ArrayStrideError { + ctx: ctx.into(), + actual_stride, + expected_stride, + element_ty: (*a.element).clone(), + } + .into()), + true => Ok(()), + } + } + SizedType::Vector(_) | SizedType::Matrix(_) | SizedType::Atomic(_) | SizedType::PackedVec(_) => { + assert_eq!(ty.byte_size(ctx.actual_repr), ty.byte_size(ctx.expected_repr)); + Ok(()) + } + } +} + +fn check_sized_fields( + ctx: LayoutContext, + s: &(impl Into + Clone), + fields: &[SizedField], + actual_offsets: &mut FieldOffsets, + expected_offsets: &mut FieldOffsets, +) -> Result<(), LayoutError> { + for (i, (field, (actual_offset, expected_offset))) in + fields.iter().zip(actual_offsets.zip(expected_offsets)).enumerate() + { + if actual_offset != expected_offset { + return Err(StructFieldOffsetError { + ctx: ctx.into(), + struct_type: s.clone().into(), + field_name: field.name.clone(), + field_index: i, + actual_offset, + expected_alignment: field.align(ctx.expected_repr), + } + .into()); + } + + check_compare_sized(ctx, &field.ty)?; + } + + Ok(()) +} + +/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. +#[derive(thiserror::Error, Debug, Clone)] +pub enum LayoutError { + #[error("{0}")] + ArrayStride(#[from] ArrayStrideError), + #[error("{0}")] + StructureFieldOffset(#[from] StructFieldOffsetError), + #[error( + "The size of `{1}` on the gpu is not known at compile time. `{0}` \ + requires that the size of uniform buffers on the gpu is known at compile time." + )] + UniformBufferMustBeSized(&'static str, LayoutableType), + #[error("{0} contains a `PackedVector`, which are not allowed in {1} address space. ")] + MayNotContainPackedVec(LayoutableType, Repr), +} + + +#[derive(Debug, Clone, Copy)] +pub struct LayoutContext<'a> { + top_level_type: &'a LayoutableType, + actual_repr: Repr, + expected_repr: Repr, + use_color: bool, +} + +#[derive(Debug, Clone)] +pub struct LayoutErrorContext { + top_level_type: LayoutableType, + actual_repr: Repr, + expected_repr: Repr, + use_color: bool, +} + +impl<'a> From> for LayoutErrorContext { + fn from(ctx: LayoutContext) -> Self { + LayoutErrorContext { + top_level_type: ctx.top_level_type.clone(), + actual_repr: ctx.actual_repr, + expected_repr: ctx.expected_repr, + use_color: ctx.use_color, + } + } +} + +impl Repr { + fn more_info_at(&self) -> &str { "https://www.w3.org/TR/WGSL/#memory-layouts" } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct ArrayStrideError { + ctx: LayoutErrorContext, + expected_stride: u64, + actual_stride: u64, + element_ty: SizedType, +} + +impl std::error::Error for ArrayStrideError {} +impl Display for ArrayStrideError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let top_level = &self.ctx.top_level_type; + writeln!( + f, + "array elements within type `{}` do not satisfy {} layout requirements.", + self.ctx.expected_repr, top_level + )?; + writeln!( + f, + "The array with `{}` elements requires stride {}, but has stride {}.", + self.element_ty, self.expected_stride, self.actual_stride + )?; + writeln!(f, "The full layout of `{}` is:", top_level)?; + let layout = TypeLayout::new_layout_for(&self.ctx.top_level_type, self.ctx.actual_repr); + layout.write("", self.ctx.use_color, f)?; + writeln!(f)?; + writeln!( + f, + "\nfor more information on the layout rules, see {}", + self.ctx.expected_repr.more_info_at() + )?; + Ok(()) + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct StructFieldOffsetError { + pub ctx: LayoutErrorContext, + pub struct_type: StructKind, + pub field_name: CanonName, + pub field_index: usize, + pub actual_offset: u64, + pub expected_alignment: U32PowerOf2, +} + +#[derive(Debug, Clone)] +pub enum StructKind { + Sized(SizedStruct), + Unsized(UnsizedStruct), +} + +impl From for StructKind { + fn from(value: SizedStruct) -> Self { StructKind::Sized(value) } +} +impl From for StructKind { + fn from(value: UnsizedStruct) -> Self { StructKind::Unsized(value) } +} + +impl std::error::Error for StructFieldOffsetError {} +impl Display for StructFieldOffsetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let top_level_type = &self.ctx.top_level_type; + let top_level_name = match top_level_type { + LayoutableType::Sized(SizedType::Struct(s)) => Some(&s.name), + LayoutableType::UnsizedStruct(s) => Some(&s.name), + _ => None, + }; + + let struct_name = match &self.struct_type { + StructKind::Sized(s) => &s.name, + StructKind::Unsized(s) => &s.name, + }; + + let is_top_level = top_level_name == Some(struct_name); + + let structure_def_location = Context::try_with(call_info!(), |ctx| -> Option<_> { + match &self.struct_type { + StructKind::Sized(s) => { + let s: ir::SizedStruct = s.clone().try_into().ok()?; + ctx.struct_registry().get(&*s).map(|def| def.call_info()) + } + StructKind::Unsized(s) => { + let s: ir::BufferBlock = s.clone().try_into().ok()?; + ctx.struct_registry().get(&*s).map(|def| def.call_info()) + } + } + }) + .flatten(); + + + writeln!( + f, + "The type `{top_level_type}` cannot be used as a {} buffer binding.", + self.ctx.expected_repr + )?; + match is_top_level { + true => write!(f, "Struct `{}`", struct_name)?, + false => write!(f, "It contains a struct `{}`, which", struct_name)?, + } + writeln!( + f, + " does not satisfy the {} memory layout requirements.", + self.ctx.expected_repr + )?; + writeln!(f)?; + + if let Some(call_info) = structure_def_location { + writeln!(f, "Definition at {call_info}")?; + } + + write_struct_layout( + &self.struct_type, + self.ctx.actual_repr, + self.ctx.use_color, + Some(self.field_index), + f, + )?; + + let actual_align = max_u64_po2_dividing(self.actual_offset); + let expected_alignment = self.expected_alignment.as_u32(); + writeln!(f)?; + set_color(f, Some("#508EE3"), false)?; + writeln!( + f, + "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", + self.field_name, expected_alignment, self.actual_offset + )?; + set_color(f, None, false)?; + writeln!(f)?; + + writeln!(f, "Potential solutions include:")?; + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", + expected_alignment, self.field_name + )?; + writeln!(f, "- use a storage binding instead of a uniform binding")?; + writeln!( + f, + "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", + self.field_name, expected_alignment + )?; + writeln!(f)?; + + writeln!( + f, + "In the {} address space, structs, arrays and array elements must be at least 16 byte aligned.", + self.ctx.expected_repr + )?; + writeln!( + f, + "More info about the {} address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + self.ctx.expected_repr + )?; + Ok(()) + } +} + +/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. +fn write_struct_layout( + struct_type: &StructKind, + repr: Repr, + colored: bool, + highlight_field: Option, + f: &mut F, +) -> std::fmt::Result +where + F: Write, +{ + let use_256_color_mode = false; + let color = |f_: &mut F, hex, field_index| match colored && Some(field_index) == highlight_field { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset = |f_: &mut F, field_index| match colored && Some(field_index) == highlight_field { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + + let (struct_name, sized_fields, mut field_offsets) = match struct_type { + StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr)), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), s.sized_field_offsets(repr)), + }; + + let indent = " "; + let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); + let header = format!("struct {} {{", struct_name); + let table_start_column = 1 + sized_fields + .iter() + .map(field_decl_line) + .map(|s| s.len()) + .max() + .unwrap_or(0) + .max(header.chars().count()); + f.write_str(&header)?; + for i in header.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "offset align size")?; + for (field_index, (field, field_offset)) in sized_fields.iter().zip(&mut field_offsets).enumerate() { + color(f, "#508EE3", field_index)?; + + let (align, size) = (field.align(repr), field.byte_size(repr)); + let decl_line = field_decl_line(field); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + writeln!(f, "{:6} {:5} {:4}", field_offset, align.as_u32(), size)?; + + reset(f, field_index)?; + } + if let StructKind::Unsized(s) = struct_type { + let field_index = sized_fields.len(); + color(f, "#508EE3", field_index)?; + + let last_field = &s.last_unsized; + let (last_field_offset, _) = s.last_field_offset_and_struct_align(field_offsets); + + let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); + f.write_str(&decl_line)?; + // write spaces to table on the right + for _ in decl_line.len()..table_start_column { + f.write_char(' ')? + } + write!(f, "{:6} {:5}", last_field_offset, last_field.align(repr).as_u64())?; + + + reset(f, field_index)?; + } + writeln!(f, "}}")?; + Ok(()) +} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index 5ece418..a7c8275 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -4,7 +4,7 @@ use super::*; #[derive(Clone)] pub struct LayoutMismatch { /// 2 (name, layout) pairs - layouts: [(String, TypeLayout); 2], + layouts: [(String, TypeLayout); 2], colored_error: bool, } @@ -65,7 +65,7 @@ impl LayoutMismatch { #[allow(clippy::needless_return)] pub(crate) fn write( indent: &str, - layouts: [(&str, &TypeLayout); 2], + layouts: [(&str, &TypeLayout); 2], colored: bool, f: &mut W, ) -> Result { @@ -431,20 +431,14 @@ impl LayoutMismatch { /// /// if the two layouts are not equal it uses the debug names in the returned /// error to tell the two layouts apart. -pub(crate) fn check_eq( - a: (&str, &TypeLayout), - b: (&str, &TypeLayout), -) -> Result<(), LayoutMismatch> +pub(crate) fn check_eq(a: (&str, &TypeLayout), b: (&str, &TypeLayout)) -> Result<(), LayoutMismatch> where - TypeLayout: PartialEq>, + TypeLayout: PartialEq, { match a.1 == b.1 { true => Ok(()), false => Err(LayoutMismatch { - layouts: [ - (a.0.into(), a.1.to_owned().into_plain()), - (b.0.into(), b.1.to_owned().into_plain()), - ], + layouts: [(a.0.into(), a.1.to_owned()), (b.0.into(), b.1.to_owned())], colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) .unwrap_or(false), }), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 5213c05..9b9b566 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -43,7 +43,7 @@ impl SizedType { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Row), + SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Column), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -55,7 +55,7 @@ impl SizedType { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(repr), - SizedType::Matrix(m) => m.align(repr, MatrixMajor::Row), + SizedType::Matrix(m) => m.align(repr, MatrixMajor::Column), SizedType::Atomic(a) => a.align(repr), SizedType::PackedVec(v) => v.align(repr), SizedType::Struct(s) => s.byte_size_and_align(repr).1, @@ -237,13 +237,14 @@ impl Matrix { pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { let (vec, _) = self.as_vector_array(major); - array_align(vec.align(repr), repr) + // AlignOf(vecR) + vec.align(repr) } const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { let (vec_len, array_len): (Len, NonZeroU32) = match major { - MatrixMajor::Row => (self.rows.as_len(), self.columns.as_non_zero_u32()), - MatrixMajor::Column => (self.columns.as_len(), self.rows.as_non_zero_u32()), + MatrixMajor::Column => (self.rows.as_len(), self.columns.as_non_zero_u32()), + MatrixMajor::Row => (self.columns.as_len(), self.rows.as_non_zero_u32()), }; ( Vector { @@ -309,13 +310,23 @@ impl RuntimeSizedArray { #[allow(missing_docs)] impl SizedField { - pub fn byte_size(&self, repr: Repr) -> u64 { self.ty.byte_size(repr) } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.ty.align(repr) } + pub fn byte_size(&self, repr: Repr) -> u64 { + LayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) + } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. + // This is intended! + LayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) + } } #[allow(missing_docs)] impl RuntimeSizedArrayField { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.array.align(repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. + // This is intended! + LayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) + } } pub const fn round_up(multiple_of: u64, n: u64) -> u64 { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 16a2c93..031de09 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -10,7 +10,7 @@ use crate::{ }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{builder::StructKind, FieldOptions}; +use super::{construction::StructKind, FieldOptions}; pub(crate) mod align_size; pub(crate) mod builder; diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 4a1a520..1edd93a 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -23,7 +23,7 @@ use layoutable::{ LayoutableType, Matrix, Vector, }; -pub(crate) mod builder; +pub(crate) mod construction; pub(crate) mod eq; pub(crate) mod layoutable; @@ -97,7 +97,7 @@ pub enum TypeLayoutSemantics { /// ); /// ``` #[derive(Clone)] -pub struct TypeLayout { +pub struct TypeLayout { /// size in bytes (Some), or unsized (None) pub byte_size: Option, /// the byte alignment @@ -106,24 +106,35 @@ pub struct TypeLayout { pub align: U32PowerOf2, /// the type contained in the bytes of this type layout pub kind: TypeLayoutSemantics, - - /// Is some for `constraint::Storage` and `constraint::Uniform`. Can be converted to a `StoreType`. - pub(crate) layoutable_type: Option, - _phantom: PhantomData, } // PartialEq, Eq, Hash for TypeLayout -impl PartialEq> for TypeLayout { - fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } +impl PartialEq for TypeLayout { + fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } } -impl Eq for TypeLayout {} -impl Hash for TypeLayout { +impl Eq for TypeLayout {} +impl Hash for TypeLayout { fn hash(&self, state: &mut H) { self.byte_size.hash(state); self.kind.hash(state); } } +/// TODO(chronicl) +#[derive(Debug, Clone)] +pub struct GpuTypeLayout { + ty: LayoutableType, + _repr: PhantomData, +} + +impl GpuTypeLayout { + /// TODO(chronicl) + pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } + /// TODO(chronicl) + pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } +} + +use repr::TypeReprStorageOrPacked; pub use repr::{TypeRepr, Repr}; /// Module for all restrictions on `TypeLayout`. pub mod repr { @@ -132,7 +143,14 @@ pub mod repr { /// Type representation used by `TypeLayout`. This provides guarantees /// about the alignment rules that the type layout adheres to. /// See [`TypeLayout`] documentation for more details. - pub trait TypeRepr: Clone + PartialEq + Eq {} + pub trait TypeRepr: Clone + PartialEq + Eq { + /// The corresponding enum variant of `Repr`. + const REPR: Repr; + } + /// TODO(chronicl) + pub trait TypeReprStorageOrPacked: TypeRepr {} + impl TypeReprStorageOrPacked for Storage {} + impl TypeReprStorageOrPacked for Packed {} /// Enum of layout rules. #[derive(Debug, Clone, Copy)] @@ -156,64 +174,38 @@ pub mod repr { pub const fn is_packed(self) -> bool { matches!(self, Repr::Packed) } } + impl std::fmt::Display for Repr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Repr::Storage => write!(f, "storage"), + Repr::Uniform => write!(f, "uniform"), + Repr::Packed => write!(f, "packed"), + } + } + } + macro_rules! type_repr { - ($($constraint:ident),*) => { + ($($repr:ident),*) => { $( /// A type representation used by `TypeLayout`. /// See [`TypeLayout`] documentation for more details. #[derive(Clone, PartialEq, Eq, Hash)] - pub struct $constraint; - impl TypeRepr for $constraint {} - )* - }; - } - type_repr!(Plain, Storage, Uniform, Packed); - - macro_rules! impl_from_into { - ($from:ident -> $($into:ident),*) => { - $( - impl From> for TypeLayout<$into> { - fn from(layout: TypeLayout<$from>) -> Self { type_layout_internal::cast_unchecked(layout) } + pub struct $repr; + impl TypeRepr for $repr { + const REPR: Repr = Repr::$repr; } )* }; } - - impl_from_into!(Storage -> Plain); - impl_from_into!(Uniform -> Plain, Storage); - impl_from_into!(Packed -> Plain); + type_repr!(Storage, Uniform, Packed); } impl TypeLayout { - pub(crate) fn new( - byte_size: Option, - byte_align: U32PowerOf2, - kind: TypeLayoutSemantics, - layoutable_type: Option, - ) -> Self { + pub(crate) fn new(byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics) -> Self { TypeLayout { byte_size, align: byte_align, kind, - layoutable_type, - _phantom: PhantomData, - } - } -} - -/// This module offers helper methods that do not adhere to the restriction of `TypeLayout. -/// The caller must uphold these restrictions themselves. -/// The main purpose of this module is to avoid repetition. -pub(in super::super::rust_types) mod type_layout_internal { - use super::*; - - pub fn cast_unchecked(layout: TypeLayout) -> TypeLayout { - TypeLayout { - byte_size: layout.byte_size, - align: layout.align, - kind: layout.kind, - layoutable_type: layout.layoutable_type, - _phantom: PhantomData, } } } @@ -257,10 +249,10 @@ pub struct ElementLayout { /// /// The element layout must be constraint::Plain because it's shared by all constraints. /// `ElementLayout` could possibly be made generic too, but it would complicate a lot. - pub ty: TypeLayout, + pub ty: TypeLayout, } -impl TypeLayout { +impl TypeLayout { /// Returns the byte size of the represented type. /// /// For sized types, this returns Some(size), while for unsized types @@ -270,16 +262,6 @@ impl TypeLayout { /// Returns the alignment requirement of the represented type. pub fn align(&self) -> U32PowerOf2 { self.align } - /// Although all TypeLayout always implement Into, this method - /// is offered to avoid having to declare that as a bound when handling generic TypeLayout. - pub fn into_plain(self) -> TypeLayout { type_layout_internal::cast_unchecked(self) } - - /// Get the `LayoutableType` this layout is based on. - /// - /// This may be `None` for `TypeLayout`. - /// `TypeLayout` may use [`TypeLayout::layoutable_type`]. - pub fn get_layoutable_type(&self) -> Option<&LayoutableType> { self.layoutable_type.as_ref() } - /// a short name for this `TypeLayout`, useful for printing inline pub fn short_name(&self) -> String { match &self.kind { @@ -345,7 +327,6 @@ impl TypeLayout { write!(f, "{indent}{offset:3} {}: ", field.name)?; field.ty.write(&(indent.to_string() + tab), colored, f)?; if let Some(size) = field.ty.byte_size { - let size = size.max(field.custom_min_size.unwrap_or(0)); write!(f, " size={size}")?; } else { write!(f, " size=?")?; @@ -374,7 +355,6 @@ impl TypeLayout { // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.align U32PowerOf2::try_from(align_of::() as u32).unwrap(), kind, - None, ) } @@ -383,7 +363,7 @@ impl TypeLayout { store_type: ir::StoreType, ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; - Ok(TypeLayout::new_storage_layout_for(t).into()) + Ok(TypeLayout::new_layout_for(&t, Repr::Storage).into()) } } @@ -391,25 +371,17 @@ impl TypeLayout { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FieldLayout { pub name: CanonName, - // whether size/align is custom doesn't matter for the layout equality. - pub custom_min_size: IgnoreInEqOrdHash>, - pub custom_min_align: IgnoreInEqOrdHash>, - /// The fields layout must be constraint::Plain, - /// because that's the most it can be while supporting all possible constraints. - pub ty: TypeLayout, + pub ty: TypeLayout, } impl FieldLayout { - fn byte_size(&self) -> Option { - self.ty - .byte_size() - .map(|byte_size| LayoutCalculator::calculate_byte_size(byte_size, self.custom_min_size.0)) - } + fn byte_size(&self) -> Option { self.ty.byte_size() } /// The alignment of the field with `custom_min_align` taken into account. - fn align(&self) -> U32PowerOf2 { LayoutCalculator::calculate_align(self.ty.align(), self.custom_min_align.0) } + fn align(&self) -> U32PowerOf2 { self.ty.align() } } +// TODO(chronicl) move into layoutable/builder.rs /// Options for the field of a struct. /// /// If you only want to customize the field's name, you can convert most string types @@ -448,13 +420,13 @@ impl> From for FieldOptions { fn from(name: T) -> Self { Self::new(name, None, None) } } -impl Display for TypeLayout { +impl Display for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); self.write("", colored, f) } } -impl Debug for TypeLayout { +impl Debug for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } } diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 4ba3e2d..7880ec3 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -3,7 +3,7 @@ use super::{ layout_traits::{FromAnys, GetAllFields, GpuLayout}, mem::{self, AddressSpace}, reference::{AccessMode, AccessModeReadable}, - type_layout::{self, type_layout_internal}, + type_layout::{self}, AsAny, GpuType, ToGpuType, }; use crate::{ diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index df5cf8f..4d065e3 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,6 +7,7 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, + type_layout::repr, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; @@ -592,7 +593,7 @@ impl GpuLayout for vec where vec: NoBools, { - fn gpu_repr() -> layout::Repr { layout::Repr::Storage } + type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 10bf448..64043f6 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -10,6 +10,7 @@ use thiserror::Error; use super::{PossibleStages, ShaderStage, StageMask}; use crate::{ + any::layout::Repr, call_info, common::{ integer::post_inc_usize, @@ -355,7 +356,7 @@ impl WipPushConstantsField { let sized_struct: layoutable::SizedStruct = sized_struct .try_into() .expect("push constants are NoBools and NoHandles"); - let layout = TypeLayout::new_storage_layout_for(sized_struct); + let layout = TypeLayout::new_layout_for(&sized_struct.into(), Repr::Storage); let layout = match &layout.kind { type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, _ => unreachable!("expected struct layout for type layout of struct"), diff --git a/shame/src/lib.rs b/shame/src/lib.rs index b5dac06..cffa057 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -467,11 +467,11 @@ pub mod any { use crate::frontend::rust_types::type_layout; // type layout pub use type_layout::TypeLayout; + pub use type_layout::GpuTypeLayout; pub use type_layout::TypeRepr; pub use type_layout::Repr; pub mod repr { use crate::frontend::rust_types::type_layout; - pub use type_layout::repr::Plain; pub use type_layout::repr::Storage; pub use type_layout::repr::Uniform; pub use type_layout::repr::Packed; diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 64a795e..3d4a57d 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -89,9 +89,8 @@ pub fn impl_for_struct( } let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); let gpu_repr_shame = match gpu_repr { - Repr::Packed => quote!( #re::Repr::Packed ), - Repr::Storage => quote!( #re::Repr::Storage ), - Repr::Uniform => quote!( #re::Repr::Uniform ), + Repr::Packed => quote!( #re::repr::Packed ), + Repr::Storage => quote!( #re::repr::Storage ), }; // #[repr(...)] @@ -260,9 +259,7 @@ pub fn impl_for_struct( #last_field_type: #re::Layoutable, #where_clause_predicates { - fn gpu_repr() -> #re::Repr { - #gpu_repr_shame - } + type GpuRepr = #gpu_repr_shame; fn cpu_type_name_and_layout() -> Option, #re::TypeLayout), #re::ArrayElementsUnsizedError>> { use #re::CpuLayout as _; diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index 0e085ed..4e23ebc 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -30,7 +30,6 @@ pub fn find_literal_list_attr( pub enum Repr { Packed, Storage, - Uniform, } pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result> { @@ -44,9 +43,6 @@ pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result Date: Mon, 26 May 2025 07:02:30 +0200 Subject: [PATCH 022/182] Fix FieldLayout construction from LayoutableType --- .../frontend/rust_types/type_layout/construction.rs | 13 +++++++++---- shame/src/frontend/rust_types/type_layout/mod.rs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 77c8178..24371b6 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -112,11 +112,16 @@ impl TypeLayout { } fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + let mut ty = TypeLayout::from_sized_type(&field.ty, repr); + // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account + // custom_min_align and custom_min_size, but field.byte_size and field.align do. + ty.byte_size = Some(field.byte_size(repr)); + ty.align = field.align(repr); FieldLayoutWithOffset { rel_byte_offset: offset, field: FieldLayout { name: field.name.clone(), - ty: TypeLayout::from_sized_type(&field.ty, repr), + ty, }, } } @@ -302,7 +307,7 @@ pub struct LayoutErrorContext { use_color: bool, } -impl<'a> From> for LayoutErrorContext { +impl From> for LayoutErrorContext { fn from(ctx: LayoutContext) -> Self { LayoutErrorContext { top_level_type: ctx.top_level_type.clone(), @@ -398,11 +403,11 @@ impl Display for StructFieldOffsetError { match &self.struct_type { StructKind::Sized(s) => { let s: ir::SizedStruct = s.clone().try_into().ok()?; - ctx.struct_registry().get(&*s).map(|def| def.call_info()) + ctx.struct_registry().get(&s).map(|def| def.call_info()) } StructKind::Unsized(s) => { let s: ir::BufferBlock = s.clone().try_into().ok()?; - ctx.struct_registry().get(&*s).map(|def| def.call_info()) + ctx.struct_registry().get(&s).map(|def| def.call_info()) } } }) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 1edd93a..65e7c13 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -363,7 +363,7 @@ impl TypeLayout { store_type: ir::StoreType, ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; - Ok(TypeLayout::new_layout_for(&t, Repr::Storage).into()) + Ok(TypeLayout::new_layout_for(&t, Repr::Storage)) } } From c22bfa7621f7a1ad4d18f25bd9c1919878f588fd Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 07:18:28 +0200 Subject: [PATCH 023/182] Change GpuLayout impl of PackedVec --- shame/src/frontend/rust_types/packed_vec.rs | 5 ++--- shame/src/lib.rs | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 88c536e..a810c05 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -151,11 +151,10 @@ impl GpuLayout for PackedVec { type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { - let sized_ty = Self::sized_ty_equivalent(); + let sized_ty: layoutable::SizedType = Self::layoutable_type_sized(); let name = sized_ty.to_string().into(); - let sized_ty: layoutable::SizedType = sized_ty.try_into().expect("PackedVec is NoBools and NoHandles"); let layout = TypeLayout::new_layout_for(&sized_ty.into(), Repr::Storage); - Some(Ok((name, layout.into()))) + Some(Ok((name, layout))) } } diff --git a/shame/src/lib.rs b/shame/src/lib.rs index cffa057..8305b7d 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -514,8 +514,6 @@ pub mod any { pub use type_layout::layoutable::array_align; pub use type_layout::layoutable::FieldOffsets; // conversion and builder errors - pub use type_layout::layoutable::ir_compat::LayoutableConversionError; - pub use type_layout::layoutable::ir_compat::ContainsBoolsError; pub use type_layout::layoutable::builder::IsUnsizedStructError; pub use type_layout::layoutable::builder::StructFromPartsError; } From 6dc9776a051839363f09180ba820b4c8ef9c5c79 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 08:02:34 +0200 Subject: [PATCH 024/182] Change gpu_layout --- shame/src/frontend/rust_types/layout_traits.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 6bcc937..ea9a2b0 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -176,9 +176,7 @@ pub trait GpuLayout: Layoutable { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` -pub fn gpu_layout() -> TypeLayout { - TypeLayout::new_layout_for(&T::layoutable_type(), ::REPR) -} +pub fn gpu_layout() -> TypeLayout { gpu_type_layout::().layout() } pub fn gpu_type_layout() -> GpuTypeLayout { GpuTypeLayout::new(T::layoutable_type()) From 3d8ddb40d1d3713d26bf057ca147d1b4f79c96fb Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 17:48:31 +0200 Subject: [PATCH 025/182] Fix FieldLayout runtime sized array construction --- shame/src/frontend/rust_types/type_layout/construction.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 24371b6..e5d8152 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -78,11 +78,17 @@ impl TypeLayout { .collect::>(); let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + + let mut ty = Self::from_runtime_sized_array(&s.last_unsized.array, repr); + // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account + // custom_min_align, but s.last_unsized.align does. + ty.align = s.last_unsized.align(repr); + fields.push(FieldLayoutWithOffset { rel_byte_offset: field_offset, field: FieldLayout { name: s.last_unsized.name.clone(), - ty: Self::from_runtime_sized_array(&s.last_unsized.array, repr), + ty, }, }); From 02dd2c5b762f45b090bc0c8cc0a4298fc51bb1c9 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 26 May 2025 20:14:47 +0200 Subject: [PATCH 026/182] Cleanup and FieldOffsets redone --- shame/src/common/proc_macro_reexports.rs | 2 +- .../rust_types/type_layout/construction.rs | 41 +++--- .../type_layout/layoutable/align_size.rs | 126 ++++++++++-------- .../type_layout/layoutable/builder.rs | 88 ++++++++++++ .../rust_types/type_layout/layoutable/mod.rs | 54 +------- .../frontend/rust_types/type_layout/mod.rs | 62 +++------ shame/src/lib.rs | 2 +- 7 files changed, 210 insertions(+), 165 deletions(-) diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index e467fd8..28e46d9 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -25,7 +25,7 @@ pub use crate::frontend::rust_types::struct_::BufferFields; pub use crate::frontend::rust_types::struct_::SizedFields; pub use crate::frontend::rust_types::type_layout::FieldLayout; pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; -pub use crate::frontend::rust_types::type_layout::FieldOptions; +pub use crate::frontend::rust_types::type_layout::layoutable::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; pub use crate::frontend::rust_types::type_layout::Repr; pub use crate::frontend::rust_types::type_layout::repr; diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index e5d8152..68947cf 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -56,9 +56,10 @@ impl TypeLayout { .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) .collect(); + let (byte_size, align) = field_offsets.struct_byte_size_and_align(); ( - field_offsets.byte_size(), - field_offsets.align(), + byte_size, + align, TLS::Structure(Rc::new(StructLayout { name: s.name.clone().into(), fields, @@ -71,13 +72,13 @@ impl TypeLayout { } fn from_unsized_struct(s: &UnsizedStruct, repr: Repr) -> Self { - let mut field_offsets = s.sized_field_offsets(repr); - let mut fields = (&mut field_offsets) + let mut field_offsets = s.field_offsets(repr); + let mut fields = (&mut field_offsets.sized_field_offsets()) .zip(s.sized_fields.iter()) .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) .collect::>(); - let (field_offset, align) = s.last_field_offset_and_struct_align(field_offsets); + let (field_offset, align) = field_offsets.last_field_offset_and_struct_align(); let mut ty = Self::from_runtime_sized_array(&s.last_unsized.array, repr); // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account @@ -184,12 +185,18 @@ fn check_layout(ty: &LayoutableType, actual_repr: Repr, expected_repr: Repr) -> match &ctx.top_level_type { LayoutableType::Sized(s) => check_compare_sized(ctx, s), LayoutableType::UnsizedStruct(s) => { - let mut actual_offsets = s.sized_field_offsets(ctx.actual_repr); - let mut expected_offsets = s.sized_field_offsets(ctx.expected_repr); - check_sized_fields(ctx, s, &s.sized_fields, &mut actual_offsets, &mut expected_offsets)?; + let mut actual_offsets = s.field_offsets(ctx.actual_repr); + let mut expected_offsets = s.field_offsets(ctx.expected_repr); + check_sized_fields( + ctx, + s, + &s.sized_fields, + actual_offsets.sized_field_offsets(), + expected_offsets.sized_field_offsets(), + )?; - let (actual_last_offset, _) = s.last_field_offset_and_struct_align(actual_offsets); - let (expected_last_offset, _) = s.last_field_offset_and_struct_align(expected_offsets); + let (actual_last_offset, _) = actual_offsets.last_field_offset_and_struct_align(); + let (expected_last_offset, _) = expected_offsets.last_field_offset_and_struct_align(); if actual_last_offset != expected_last_offset { let field = &s.last_unsized; @@ -256,8 +263,8 @@ fn check_sized_fields( ctx: LayoutContext, s: &(impl Into + Clone), fields: &[SizedField], - actual_offsets: &mut FieldOffsets, - expected_offsets: &mut FieldOffsets, + actual_offsets: impl Iterator, + expected_offsets: impl Iterator, ) -> Result<(), LayoutError> { for (i, (field, (actual_offset, expected_offset))) in fields.iter().zip(actual_offsets.zip(expected_offsets)).enumerate() @@ -510,8 +517,12 @@ where }; let (struct_name, sized_fields, mut field_offsets) = match struct_type { - StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr)), - StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), s.sized_field_offsets(repr)), + StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr).into_sized_fields()), + StructKind::Unsized(s) => ( + &s.name, + s.sized_fields.as_slice(), + s.field_offsets(repr).into_sized_fields(), + ), }; let indent = " "; @@ -548,7 +559,7 @@ where color(f, "#508EE3", field_index)?; let last_field = &s.last_unsized; - let (last_field_offset, _) = s.last_field_offset_and_struct_align(field_offsets); + let (last_field_offset, _) = s.field_offsets(repr).last_field_offset_and_struct_align(); let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); f.write_str(&decl_line)?; diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 9b9b566..7421439 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -74,16 +74,10 @@ impl SizedType { impl SizedStruct { /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the - /// fields of this struct, as well as a `byte_size` and `align` calculator. - /// See the documentation of `FieldOffsets` on how to obtain `byte_size` and `align` - /// of this struct from it. - pub fn field_offsets(&self, repr: Repr) -> FieldOffsets { - FieldOffsets { - fields: &self.fields, - field_index: 0, - calc: LayoutCalculator::new(repr), - repr, - } + /// fields of this struct. `SizedStruct::byte_size_and_align_from_offsets` can be + /// used to efficiently obtain the byte_size + pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsSized { + FieldOffsetsSized(FieldOffsets::new(self.fields(), repr)) } /// Returns (byte_size, align) @@ -93,26 +87,17 @@ impl SizedStruct { /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { - let mut field_offsets = self.field_offsets(repr); - (&mut field_offsets).count(); // &mut so it doesn't consume - (field_offsets.byte_size(), field_offsets.align()) + self.field_offsets(repr).struct_byte_size_and_align() } } -/// An iterator over the field offsets of a `SizedStruct` or the sized fields of an `UnsizedStruct`. -/// -/// `FieldOffsets::byte_size` and `FieldOffsets::byte_align` can be used to query the struct's -/// `byte_size` and `byte_align`, but only takes into account the fields that have been iterated over. -/// -/// Use [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset -/// and the struct's align for `UnsizedStruct`s. +/// An iterator over the offsets of sized fields. pub struct FieldOffsets<'a> { fields: &'a [SizedField], field_index: usize, calc: LayoutCalculator, repr: Repr, } - impl Iterator for FieldOffsets<'_> { type Item = u64; @@ -127,49 +112,84 @@ impl Iterator for FieldOffsets<'_> { }) } } +impl<'a> FieldOffsets<'a> { + fn new(fields: &'a [SizedField], repr: Repr) -> Self { + Self { + fields, + field_index: 0, + calc: LayoutCalculator::new(repr), + repr, + } + } +} -impl FieldOffsets<'_> { - /// Returns the byte size of the struct, but ONLY with the fields that have been iterated over so far. - pub const fn byte_size(&self) -> u64 { self.calc.byte_size() } - /// Returns the byte align of the struct, but ONLY with the fields that have been iterated over so far. - pub const fn align(&self) -> U32PowerOf2 { struct_align(self.calc.align(), self.repr) } +/// Iterator over the field offsets of a `SizedStruct`. +// The difference to `FieldOffsets` is that it also offers a `struct_byte_size_and_align` method. +pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); +impl Iterator for FieldOffsetsSized<'_> { + type Item = u64; + fn next(&mut self) -> Option { self.0.next() } } +impl<'a> FieldOffsetsSized<'a> { + /// Consumes self and calculates the byte size and align of a struct + /// with exactly the sized fields that this FieldOffsets was created with. + pub fn struct_byte_size_and_align(mut self) -> (u64, U32PowerOf2) { + // Finishing layout calculations + (&mut self.0).count(); + (self.0.calc.byte_size(), struct_align(self.0.calc.align(), self.0.repr)) + } + /// Returns the inner iterator over sized fields. + pub fn into_sized_fields(self) -> FieldOffsets<'a> { self.0 } +} -impl UnsizedStruct { - /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the - /// sized fields of this struct. `FieldOffsets` may be also passed to - /// [`UnsizedStruct::last_field_offset_and_struct_align`] to obtain the last field's offset - /// and the struct's align. - pub fn sized_field_offsets(&self, repr: Repr) -> FieldOffsets { - FieldOffsets { - fields: &self.sized_fields, - field_index: 0, - calc: LayoutCalculator::new(repr), - repr, +/// The field offsets of an `UnsizedStruct`. +/// +/// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. +/// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset +/// and the struct's align +pub struct FieldOffsetsUnsized<'a> { + sized: FieldOffsets<'a>, + last_unsized: &'a RuntimeSizedArrayField, +} + +impl<'a> FieldOffsetsUnsized<'a> { + fn new(sized_fields: &'a [SizedField], last_unsized: &'a RuntimeSizedArrayField, repr: Repr) -> Self { + Self { + sized: FieldOffsets::new(sized_fields, repr), + last_unsized, } } - /// Returns (last field offset, byte align of unsized struct) - /// - /// `sized_field_offsets` must be from the same `UnsizedStruct`, otherwise the returned values - /// are not accurate. - pub fn last_field_offset_and_struct_align(&self, sized_field_offsets: FieldOffsets) -> (u64, U32PowerOf2) { - let mut offsets = sized_field_offsets; - // Iterating over any remaining field offsets to update the layout calculator. - (&mut offsets).count(); - - let array_align = self.last_unsized.array.align(offsets.repr); + /// Returns an iterator over the sized field offsets. + pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { &mut self.sized } + + /// Returns the last field's offset and the struct's align. + pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { + // Finishing layout calculations + (&mut self.sized).count(); + let array_align = self.last_unsized.array.align(self.sized.repr); let custom_min_align = self.last_unsized.custom_min_align; - let (offset, align) = offsets.calc.extend_unsized(array_align, custom_min_align); - (offset, struct_align(align, offsets.repr)) + let (offset, align) = self.sized.calc.extend_unsized(array_align, custom_min_align); + (offset, struct_align(align, self.sized.repr)) } - /// This is expensive as it calculates the byte align from scratch. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - let offsets = self.sized_field_offsets(repr); - self.last_field_offset_and_struct_align(offsets).1 + /// Returns the inner iterator over sized fields. + pub fn into_sized_fields(self) -> FieldOffsets<'a> { self.sized } +} + +impl UnsizedStruct { + /// Returns [`FieldOffsetsUnsized`]. + /// + /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. + /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset + /// and the struct's align + pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsUnsized { + FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, repr) } + + /// This is expensive as it calculates the byte align from scratch. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } } const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index 17b67ec..399999f 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -156,6 +156,56 @@ impl TryFrom for SizedOrArray { } } +impl SizedField { + /// Creates a new `SizedField`. + pub fn new(options: impl Into, ty: impl Into) -> Self { + let options = options.into(); + Self { + name: options.name, + custom_min_size: options.custom_min_size, + custom_min_align: options.custom_min_align, + ty: ty.into(), + } + } +} + +impl RuntimeSizedArrayField { + /// Creates a new `RuntimeSizedArrayField` given it's field name, + /// an optional custom minimum align and it's element type. + pub fn new( + name: impl Into, + custom_min_align: Option, + element_ty: impl Into, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + array: RuntimeSizedArray { + element: element_ty.into(), + }, + } + } +} + +impl SizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type and length. + pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { + Self { + element: Rc::new(element_ty.into()), + len, + } + } +} + +impl RuntimeSizedArray { + /// Creates a new `RuntimeSizedArray` from it's element type. + pub fn new(element_ty: impl Into) -> Self { + RuntimeSizedArray { + element: element_ty.into(), + } + } +} + impl> From<(T, SizedType)> for SizedField { fn from(value: (T, SizedType)) -> Self { Self { @@ -176,3 +226,41 @@ impl> From<(T, SizedType)> for RuntimeSizedArrayField { } } } + +/// Options for the field of a struct. +/// +/// If you only want to customize the field's name, you can convert most string types +/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, +/// meaning you can just pass the string type directly. +#[derive(Debug, Clone)] +pub struct FieldOptions { + /// Name of the field + pub name: CanonName, + /// Custom minimum align of the field. + pub custom_min_align: Option, + /// Custom mininum size of the field. + pub custom_min_size: Option, +} + +impl FieldOptions { + /// Creates new `FieldOptions`. + /// + /// If you only want to customize the field's name, you can convert most string types + /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, + /// meaning you can just pass the string type directly. + pub fn new( + name: impl Into, + custom_min_align: Option, + custom_min_size: Option, + ) -> Self { + Self { + name: name.into(), + custom_min_align, + custom_min_size, + } + } +} + +impl> From for FieldOptions { + fn from(name: T) -> Self { Self::new(name, None, None) } +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 031de09..8f92197 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -10,14 +10,14 @@ use crate::{ }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{construction::StructKind, FieldOptions}; +use super::{construction::StructKind}; pub(crate) mod align_size; pub(crate) mod builder; pub(crate) mod ir_compat; pub use align_size::{FieldOffsets, MatrixMajor, LayoutCalculator, array_size, array_stride, array_align}; -pub use builder::SizedOrArray; +pub use builder::{SizedOrArray, FieldOptions}; /// Types that have a defined memory layout. /// @@ -133,56 +133,6 @@ pub struct RuntimeSizedArrayField { pub array: RuntimeSizedArray, } -impl SizedField { - /// Creates a new `SizedField`. - pub fn new(options: impl Into, ty: impl Into) -> Self { - let options = options.into(); - Self { - name: options.name, - custom_min_size: options.custom_min_size, - custom_min_align: options.custom_min_align, - ty: ty.into(), - } - } -} - -impl RuntimeSizedArrayField { - /// Creates a new `RuntimeSizedArrayField` given it's field name, - /// an optional custom minimum align and it's element type. - pub fn new( - name: impl Into, - custom_min_align: Option, - element_ty: impl Into, - ) -> Self { - Self { - name: name.into(), - custom_min_align, - array: RuntimeSizedArray { - element: element_ty.into(), - }, - } - } -} - -impl SizedArray { - /// Creates a new `RuntimeSizedArray` from it's element type and length. - pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { - Self { - element: Rc::new(element_ty.into()), - len, - } - } -} - -impl RuntimeSizedArray { - /// Creates a new `RuntimeSizedArray` from it's element type. - pub fn new(element_ty: impl Into) -> Self { - RuntimeSizedArray { - element: element_ty.into(), - } - } -} - /// Trait for types that have a well-defined memory layout. pub trait Layoutable { /// Returns the `LayoutableType` representation for this type. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 65e7c13..19c9085 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -120,7 +120,21 @@ impl Hash for TypeLayout { } } -/// TODO(chronicl) +/// A `TypeLayout`, but guaranteed to be based on a `LayoutableType` and +/// a `Repr` - it follows the layout rules that correspond to the `Repr`. +/// +/// The actual `TypeLayout` can be obtained via `GpuTypeLayout::layout`. +/// +/// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created +/// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by +/// using `TryFrom::try_from` on a `GpuTypeLayout`. This is the case, because +/// shame does not support `#[gpu_repr(uniform)]`, meaning we can't lay out a type +/// according to uniform layout rules, we must lay it out according to storage layout rules +/// and then check whether the storage layout also follows the uniform layout rules. +// The reason shame doesn't support #[gpu_repr(uniform)] is that wgsl does not have +// a custom stride attribute, so for example the elements in the `sm::Array` in +// struct A { a: sm::Array } need to have a stride of 16 according to uniform +// layout rules, which we can't enforce. #[derive(Debug, Clone)] pub struct GpuTypeLayout { ty: LayoutableType, @@ -128,9 +142,9 @@ pub struct GpuTypeLayout { } impl GpuTypeLayout { - /// TODO(chronicl) + /// Gets the `TypeLayout`. pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } - /// TODO(chronicl) + /// Returns the `LayoutableType` this `GpuTypeLayout` is based on. pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } } @@ -147,7 +161,8 @@ pub mod repr { /// The corresponding enum variant of `Repr`. const REPR: Repr; } - /// TODO(chronicl) + /// A subset of the types implementing `TypeRepr`. As the name suggests + /// only `Storage` and `Packed` implement this trait. pub trait TypeReprStorageOrPacked: TypeRepr {} impl TypeReprStorageOrPacked for Storage {} impl TypeReprStorageOrPacked for Packed {} @@ -381,45 +396,6 @@ impl FieldLayout { fn align(&self) -> U32PowerOf2 { self.ty.align() } } -// TODO(chronicl) move into layoutable/builder.rs -/// Options for the field of a struct. -/// -/// If you only want to customize the field's name, you can convert most string types -/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, -/// meaning you can just pass the string type directly. -#[derive(Debug, Clone)] -pub struct FieldOptions { - /// Name of the field - pub name: CanonName, - /// Custom minimum align of the field. - pub custom_min_align: Option, - /// Custom mininum size of the field. - pub custom_min_size: Option, -} - -impl FieldOptions { - /// Creates new `FieldOptions`. - /// - /// If you only want to customize the field's name, you can convert most string types - /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, - /// meaning you can just pass the string type directly. - pub fn new( - name: impl Into, - custom_min_align: Option, - custom_min_size: Option, - ) -> Self { - Self { - name: name.into(), - custom_min_align, - custom_min_size, - } - } -} - -impl> From for FieldOptions { - fn from(name: T) -> Self { Self::new(name, None, None) } -} - impl Display for TypeLayout { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 8305b7d..d38dc75 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -480,7 +480,6 @@ pub mod any { pub use type_layout::StructLayout; pub use type_layout::FieldLayoutWithOffset; pub use type_layout::FieldLayout; - pub use type_layout::FieldOptions; pub use type_layout::ElementLayout; // layoutable traits pub use type_layout::layoutable::Layoutable; @@ -507,6 +506,7 @@ pub mod any { pub use type_layout::layoutable::RuntimeSizedArrayField; pub use type_layout::layoutable::CanonName; pub use type_layout::layoutable::SizedOrArray; + pub use type_layout::layoutable::FieldOptions; // layout calculation utility pub use type_layout::layoutable::LayoutCalculator; pub use type_layout::layoutable::array_stride; From 23af34a96a21411d22b71e9cda629d9e363ad5fa Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 27 May 2025 05:51:47 +0200 Subject: [PATCH 027/182] Fix repr_c_struct_layout --- shame/src/common/proc_macro_utils.rs | 6 ++++-- shame/tests/test_layout.rs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 164aecb..797f3ec 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -100,8 +100,10 @@ pub fn repr_c_struct_layout( let total_struct_size = last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); - let new_size = |layout_size: Option, actual_size: Option| { - (layout_size != actual_size).then_some(actual_size).flatten() + let new_size = |layout_size: Option, actual_size: Option| match (layout_size, actual_size) { + (_, Some(s)) => Some(s), // prefer actual size + (Some(s), None) => Some(s), // but still use layout size if no actual size + (None, None) => None, }; let mut fields = first_fields_with_offsets_and_sizes .iter() diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 041d20d..85115ed 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -228,6 +228,8 @@ fn unsized_struct_vec3_align_layout_eq() { a: f32x4_align4, // size=16, align=4 } + println!("{}", cpu_layout::()); + // the alignment on the top level of the layout doesn't matter. // two layouts are only considered different if an alignment mismatch // leads to different offsets of fields or array elements From 6c3d170d6f0f3eb56b7cca1577327108cb49f8d5 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 29 May 2025 20:16:28 +0200 Subject: [PATCH 028/182] Some comment cleanup --- .../rust_types/type_layout/construction.rs | 7 ++- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 43 +++++++++---------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 68947cf..e586494 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -14,8 +14,8 @@ use super::{ use TypeLayoutSemantics as TLS; impl TypeLayout { - /// Returns the type layout of the given `LayoutableType` - /// layed out according to the given `repr`. + /// Returns the type layout of the `LayoutableType` + /// layed out according to the `repr`. pub fn new_layout_for(ty: &LayoutableType, repr: Repr) -> Self { match ty { LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), @@ -287,7 +287,7 @@ fn check_sized_fields( Ok(()) } -/// Enum of possible errors during `TypeLayout -> TypeLayout` conversion. +/// Enum of possible errors during comparison of two layouts for the same `LayoutableType`. #[derive(thiserror::Error, Debug, Clone)] pub enum LayoutError { #[error("{0}")] @@ -495,7 +495,6 @@ impl Display for StructFieldOffsetError { } } -/// Panics if s is not the layout of a struct and doesn't contain a cpu-shareable. fn write_struct_layout( struct_type: &StructKind, repr: Repr, diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 8f92197..0bbf272 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -22,7 +22,7 @@ pub use builder::{SizedOrArray, FieldOptions}; /// Types that have a defined memory layout. /// /// `LayoutableType` does not contain any layout information itself, but a layout -/// can be assigned to it using [`TypeLayout`] according to one of the available layout rules: +/// can be assigned to it using [`GpuTypeLayout`] according to one of the available layout rules: /// storage, uniform or packed. #[derive(Debug, Clone)] pub enum LayoutableType { diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 19c9085..81fac77 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -57,7 +57,6 @@ pub enum TypeLayoutSemantics { /// The following types implementing `TypeRepr` exist and can be found in [`shame::any::repr`]: /// /// ``` -/// struct Plain; /// May not follow any layout rules. This is the default. /// struct Storage; /// wgsl storage address space layout / OpenGL std430 /// struct Uniform; /// wgsl uniform address space layout / OpenGL std140 /// struct Packed; /// Packed layout @@ -67,19 +66,22 @@ pub enum TypeLayoutSemantics { /// /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints /// -/// The following methods exist for creating new type layouts based on a [`LayoutableType`] +/// The following method exists for creating new type layouts based on a [`LayoutableType`] /// ``` /// let layout_type: LayoutableType = f32x1::layoutable_type(); -/// let _ = TypeLayout::new_layout_for(layout_type); -/// let _ = TypeLayout::new_storage_layout_for(layout_type); -/// let _ = TypeLayout::new_uniform_layout_for(layout_type); -/// let _ = TypeLayout::new_packed_layout_for(layout_type); +/// let repr = Repr::Storage; // or Uniform or Packed +/// let _ = TypeLayout::new_layout_for(layout_type, repr); /// ``` /// -/// Non-`Plain` layouts are always based on a [`LayoutableType`], which can be accessed -/// using [`TypeLayout::layoutable_type`]. They also guarantee, that all nested `TypeLayout`s -/// (for example struct fields) are also based on a `LayoutableType`, which can only be accessed -/// through `TypeLayout::get_layoutable_type`. +/// The resulting layout will always follow the layout rules of the `Repr`, however, this +/// can result in layouts that are not representable in wgsl, such as the uniform layout for +/// `shame::Array`, which requires at least a 16 byte stride. The `TypeLayout` will +/// contain information for the correct minimum stride, but since wgsl does not have a custom +/// stride attribute (like `@align` or `@size` but for strides) the type layout can't be +/// translated to wgsl. +/// +/// For the above reason `TypeLayout` exists mainly for internal usage in shame and +/// [`GpuTypeLayout`] is the user interface. See it's documentation for more information. /// /// ### Layout comparison /// @@ -127,14 +129,9 @@ impl Hash for TypeLayout { /// /// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created /// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by -/// using `TryFrom::try_from` on a `GpuTypeLayout`. This is the case, because -/// shame does not support `#[gpu_repr(uniform)]`, meaning we can't lay out a type -/// according to uniform layout rules, we must lay it out according to storage layout rules -/// and then check whether the storage layout also follows the uniform layout rules. -// The reason shame doesn't support #[gpu_repr(uniform)] is that wgsl does not have -// a custom stride attribute, so for example the elements in the `sm::Array` in -// struct A { a: sm::Array } need to have a stride of 16 according to uniform -// layout rules, which we can't enforce. +/// using `TryFrom::try_from` on a `GpuTypeLayout`, which only checks whether +/// the storage layout also follows the uniform layout rules - it does not change the +/// corresponding `TypeLayout`. #[derive(Debug, Clone)] pub struct GpuTypeLayout { ty: LayoutableType, @@ -150,13 +147,13 @@ impl GpuTypeLayout { use repr::TypeReprStorageOrPacked; pub use repr::{TypeRepr, Repr}; -/// Module for all restrictions on `TypeLayout`. +/// Module for all restrictions on `GpuTypeLayout`. pub mod repr { use super::*; - /// Type representation used by `TypeLayout`. This provides guarantees + /// Type representation used by `GpuTypeLayout`. This provides guarantees /// about the alignment rules that the type layout adheres to. - /// See [`TypeLayout`] documentation for more details. + /// See [`GpuTypeLayout`] documentation for more details. pub trait TypeRepr: Clone + PartialEq + Eq { /// The corresponding enum variant of `Repr`. const REPR: Repr; @@ -202,8 +199,8 @@ pub mod repr { macro_rules! type_repr { ($($repr:ident),*) => { $( - /// A type representation used by `TypeLayout`. - /// See [`TypeLayout`] documentation for more details. + /// A type representation used by `GpuTypeLayout`. + /// See [`GpuTypeLayout`] documentation for more details. #[derive(Clone, PartialEq, Eq, Hash)] pub struct $repr; impl TypeRepr for $repr { From a9abd26040e331e96d274c3847339aa08063db5f Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sat, 28 Jun 2025 22:23:42 +0200 Subject: [PATCH 029/182] . --- examples/api_showcase/src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index 0d25789..db05c00 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -1,5 +1,4 @@ #![allow(unused, clippy::no_effect)] -use shame::gpu_layout; use shame as sm; use shame::prelude::*; use shame::aliases::*; @@ -487,13 +486,13 @@ struct Mat2([[f32; 2]; 2]); // tell `shame` about the layout semantics of your cpu types // Mat2::layout() == sm::f32x2x2::layout() impl sm::CpuLayout for Mat2 { - fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } + fn cpu_layout() -> sm::TypeLayout { sm::gpu_layout::() } } #[repr(C, align(16))] struct Mat4([[f32; 4]; 4]); impl sm::CpuLayout for Mat4 { - fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } + fn cpu_layout() -> sm::TypeLayout { sm::gpu_layout::() } } // using "duck-traiting" allows you to define layouts for foreign cpu-types, From 77f7b40f1a4941b47aba1401857f15fd0139b2b5 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sat, 28 Jun 2025 22:36:44 +0200 Subject: [PATCH 030/182] . --- examples/api_showcase/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index db05c00..9f19cb9 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -508,5 +508,5 @@ impl sm::CpuLayout for Mat4 { // // tell `shame` about the layout semantics of `glam` types // impl MyCpuLayoutTrait for glam::Mat4 { -// fn layout() -> shame::TypeLayout { sm::f32x4x4::layout() } +// fn layout() -> shame::TypeLayout { sm::gpu_layout::() } // } From cba751c7c1adfef8950630511234b5c5b494c758 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sat, 28 Jun 2025 22:38:14 +0200 Subject: [PATCH 031/182] . --- examples/hello_triangles/src/util/shame_glam.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/hello_triangles/src/util/shame_glam.rs b/examples/hello_triangles/src/util/shame_glam.rs index faebf6c..cff249b 100644 --- a/examples/hello_triangles/src/util/shame_glam.rs +++ b/examples/hello_triangles/src/util/shame_glam.rs @@ -2,7 +2,7 @@ //! //! implement `shame::CpuLayout` for some glam types. -use shame::{self as sm, gpu_layout}; +use shame as sm; use sm::GpuLayout; /// circumventing the orphan rule by defining our own trait. @@ -14,14 +14,14 @@ pub trait CpuLayoutExt { // glam::Vec4 matches sm::f32x4 in size and alignment impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } + fn cpu_layout() -> sm::TypeLayout { sm::gpu_layout::() } } // glam::Vec2 only matches sm::f32x2 if it has 8 byte alignment impl CpuLayoutExt for glam::Vec2 { fn cpu_layout() -> sm::TypeLayout { if align_of::() == 8 { - gpu_layout::() + sm::gpu_layout::() } else { panic!("glam needs to use the `cuda` crate feature for Vec2 to be 8 byte aligned"); } @@ -30,5 +30,5 @@ impl CpuLayoutExt for glam::Vec2 { // glam::Mat4 matches sm::f32x4x4 in size and alignment impl CpuLayoutExt for glam::Mat4 { - fn cpu_layout() -> sm::TypeLayout { gpu_layout::() } + fn cpu_layout() -> sm::TypeLayout { sm::gpu_layout::() } } From 0dc1ed8171cc50e93071f388d57f4623050ebf7e Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sat, 28 Jun 2025 23:08:15 +0200 Subject: [PATCH 032/182] . --- examples/type_layout/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 35830bc..72c6d6d 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -45,7 +45,7 @@ fn main() { // // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) - // This has align of 4 for storage and align of 16 for uniform. + // This has align of 4, which is ok for putting into `Storage` but `Uniform` memory requires 16 byte alignment of arrays .extend("b", Array::>::layoutable_type_sized()); let storage = GpuTypeLayout::::new(sized_struct.clone()); From 3b5a7d68bfc2ca1c0b08e0201e48aac96ce5883f Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 07:58:52 +0200 Subject: [PATCH 033/182] Make ReprCField.alignment a U32PowerOf2 --- examples/type_layout/src/main.rs | 2 +- shame/src/common/po2.rs | 3 +++ shame/src/common/proc_macro_reexports.rs | 1 + shame/src/common/proc_macro_utils.rs | 9 ++++----- shame/src/frontend/rust_types/layout_traits.rs | 16 ++++++++++------ shame_derive/src/derive_layout.rs | 6 ++++-- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 72c6d6d..3c71232 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -45,7 +45,7 @@ fn main() { // // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) - // This has align of 4, which is ok for putting into `Storage` but `Uniform` memory requires 16 byte alignment of arrays + // This has align of 4, which is ok for putting into `Storage` but `Uniform` memory requires 16 byte alignment of arrays .extend("b", Array::>::layoutable_type_sized()); let storage = GpuTypeLayout::::new(sized_struct.clone()); diff --git a/shame/src/common/po2.rs b/shame/src/common/po2.rs index accf6a2..6382071 100644 --- a/shame/src/common/po2.rs +++ b/shame/src/common/po2.rs @@ -141,6 +141,9 @@ impl U32PowerOf2 { n => return None, }) } + + /// Tries to convert a usize to U32PowerOf2. + pub const fn try_from_usize(value: usize) -> Option { Self::try_from_u32(value as u32) } } impl TryFrom for U32PowerOf2 { diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 28e46d9..2b2e71e 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -61,3 +61,4 @@ pub use crate::ir::pipeline::StageMask; pub use crate::ir::recording::CallInfo; pub use crate::ir::recording::CallInfoScope; pub use crate::mem::AddressSpace; +pub use crate::any::U32PowerOf2; diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 797f3ec..e39d017 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -57,7 +57,7 @@ pub fn collect_into_array_exact(mut it: impl Iterator, + repr_c_align_attribute: Option, struct_name: &'static str, first_fields_with_offsets_and_sizes: &[(ReprCField, usize, usize)], mut last_field: ReprCField, @@ -79,7 +79,7 @@ pub fn repr_c_struct_layout( return Err(ReprCError::SecondLastElementIsUnsized); }; round_up( - last_field.alignment as u64, + last_field.alignment.as_u64(), *_2nd_last_offset as u64 + *_2nd_last_size as u64, ) } @@ -88,13 +88,12 @@ pub fn repr_c_struct_layout( let max_alignment = first_fields_with_offsets_and_sizes .iter() .map(|(f, _, _)| f.alignment) - .fold(last_field.alignment, ::std::cmp::max) as u64; + .fold(last_field.alignment, ::std::cmp::max); let struct_alignment = match repr_c_align_attribute { Some(repr_c_align) => max_alignment.max(repr_c_align), None => max_alignment, }; - let struct_alignment = U32PowerOf2::try_from(struct_alignment as u32).unwrap(); let last_field_size = last_field_size.map(|s| s as u64); let total_struct_size = diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index ea9a2b0..ca2309a 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -746,24 +746,28 @@ impl CpuLayout for i32 { /// (no documentation yet) pub trait CpuAligned { /// (no documentation yet) - const CPU_ALIGNMENT: usize; + const CPU_ALIGNMENT: U32PowerOf2; /// (no documentation yet) const CPU_SIZE: Option; /// (no documentation yet) - fn alignment() -> usize; + fn alignment() -> U32PowerOf2; } impl CpuAligned for T { - const CPU_ALIGNMENT: usize = std::mem::align_of::(); + const CPU_ALIGNMENT: U32PowerOf2 = + U32PowerOf2::try_from_usize(std::mem::align_of::()).expect("alignment of types is always a power of 2"); const CPU_SIZE: Option = Some(std::mem::size_of::()); - fn alignment() -> usize { std::mem::align_of::() } + fn alignment() -> U32PowerOf2 { Self::CPU_ALIGNMENT } } impl CpuAligned for [T] { // must be same as align of `T` since `std::slice::from_ref` and `&slice[0]` exist - const CPU_ALIGNMENT: usize = std::mem::align_of::(); + const CPU_ALIGNMENT: U32PowerOf2 = T::CPU_ALIGNMENT; const CPU_SIZE: Option = None; - fn alignment() -> usize { std::mem::align_of_val::<[T]>(&[]) } + fn alignment() -> U32PowerOf2 { + U32PowerOf2::try_from_usize(std::mem::align_of_val::<[T]>(&[])) + .expect("alignment of types is always a power of 2") + } } impl CpuLayout for [T; N] { diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 3d4a57d..304e82f 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -156,7 +156,7 @@ pub fn impl_for_struct( }; if let Some((span, align_lit)) = &fwa.align { - match align_lit.base10_parse::().map(u32::is_power_of_two) { + match align_lit.base10_parse().map(u32::is_power_of_two) { Ok(true) => (), Ok(false) => bail!(*span, "alignment attribute must be a power of two"), Err(_) => bail!( @@ -585,7 +585,9 @@ pub fn impl_for_struct( WhichDerive::CpuLayout => { let align_attr_or_none = match repr_align_attr { None => quote!(None), - Some(n) => quote!(Some(#n as u64)), + Some(n) => quote!( + Some(#re::U32PowerOf2::try_from_usize(#n)).expect("rust checks that N in repr(C, align(N)) is a power of 2.") + ), }; Ok(quote! { From db74edb2f85ad114e46a97a183014bd7a968eb12 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 08:30:13 +0200 Subject: [PATCH 034/182] Cleanup repr_c_struct_layout --- shame/src/common/proc_macro_utils.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index e39d017..b88d7a6 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -106,13 +106,14 @@ pub fn repr_c_struct_layout( }; let mut fields = first_fields_with_offsets_and_sizes .iter() - .map(|(field, offset, size)| (field.clone(), *offset as u64, *size as u64)) + .map(|(field, offset, size)| (field, *offset as u64, *size as u64)) .map(|(mut field, offset, size)| { - field.layout.byte_size = new_size(field.layout.byte_size(), Some(size)); + let mut layout = field.layout.clone(); + layout.byte_size = new_size(field.layout.byte_size(), Some(size)); FieldLayoutWithOffset { field: FieldLayout { name: field.name.into(), - ty: field.layout.clone(), + ty: layout, }, rel_byte_offset: offset, } From 6c9da5f461d9e84efa91c2015cbf8242c624006f Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 09:47:47 +0200 Subject: [PATCH 035/182] GpuRepr clarification documented --- shame/src/frontend/rust_types/layout_traits.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index ca2309a..c5ecf3a 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -140,7 +140,9 @@ use std::rc::Rc; /// pub trait GpuLayout: Layoutable { /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. - // fn gpu_repr() -> Repr; + /// + /// `GpuRepr` only exists so a (derived) struct can be packed for use in vertex buffer usage. + /// It is `repr::Storage` in all other cases. type GpuRepr: TypeReprStorageOrPacked; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a From 673c007ddce6d7011e0400192653bb9e814c2256 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 20:56:14 +0200 Subject: [PATCH 036/182] . --- shame/src/frontend/rust_types/array.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 8899dc4..3ddbac7 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -202,7 +202,9 @@ impl GpuLayout t_cpu_layout.align(), TypeLayoutSemantics::Array( Rc::new(ElementLayout { - byte_stride: layoutable::array_stride(t_cpu_layout.align(), t_cpu_size), + // array stride is element size according to + // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.size + byte_stride: t_cpu_size, ty: t_cpu_layout, }), N::LEN.map(NonZeroU32::get), From 603a213412fca75b5f95bc03d123c7e3ffb18705 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:14:40 +0200 Subject: [PATCH 037/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 81fac77..a6d3163 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -91,12 +91,8 @@ pub enum TypeLayoutSemantics { /// /// a layout comparison looks like this: /// ``` -/// assert!(f32::cpu_layout() == vec::gpu_layout()); -/// // or, more explicitly -/// assert_eq!( -/// ::cpu_layout(), -/// as GpuLayout>::gpu_layout(), -/// ); +/// use shame as sm; +/// assert_eq!(sm::cpu_layout::(), sm::gpu_layout>()); /// ``` #[derive(Clone)] pub struct TypeLayout { From 8c2adb302bf61061b127a6108ea1a3e387aac2f3 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:19:35 +0200 Subject: [PATCH 038/182] . --- .../rust_types/type_layout/construction.rs | 4 ++-- .../frontend/rust_types/type_layout/mod.rs | 20 ++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index e586494..576f6fa 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -83,7 +83,7 @@ impl TypeLayout { let mut ty = Self::from_runtime_sized_array(&s.last_unsized.array, repr); // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account // custom_min_align, but s.last_unsized.align does. - ty.align = s.last_unsized.align(repr); + ty.align = s.last_unsized.align(repr).into(); fields.push(FieldLayoutWithOffset { rel_byte_offset: field_offset, @@ -123,7 +123,7 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account // custom_min_align and custom_min_size, but field.byte_size and field.align do. ty.byte_size = Some(field.byte_size(repr)); - ty.align = field.align(repr); + ty.align = field.align(repr).into(); FieldLayoutWithOffset { rel_byte_offset: offset, field: FieldLayout { diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index a6d3163..1134e2d 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -94,30 +94,18 @@ pub enum TypeLayoutSemantics { /// use shame as sm; /// assert_eq!(sm::cpu_layout::(), sm::gpu_layout>()); /// ``` -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct TypeLayout { /// size in bytes (Some), or unsized (None) pub byte_size: Option, /// the byte alignment /// /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) - pub align: U32PowerOf2, + pub align: IgnoreInEqOrdHash, /// the type contained in the bytes of this type layout pub kind: TypeLayoutSemantics, } -// PartialEq, Eq, Hash for TypeLayout -impl PartialEq for TypeLayout { - fn eq(&self, other: &TypeLayout) -> bool { self.byte_size() == other.byte_size() && self.kind == other.kind } -} -impl Eq for TypeLayout {} -impl Hash for TypeLayout { - fn hash(&self, state: &mut H) { - self.byte_size.hash(state); - self.kind.hash(state); - } -} - /// A `TypeLayout`, but guaranteed to be based on a `LayoutableType` and /// a `Repr` - it follows the layout rules that correspond to the `Repr`. /// @@ -212,7 +200,7 @@ impl TypeLayout { pub(crate) fn new(byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics) -> Self { TypeLayout { byte_size, - align: byte_align, + align: byte_align.into(), kind, } } @@ -268,7 +256,7 @@ impl TypeLayout { pub fn byte_size(&self) -> Option { self.byte_size } /// Returns the alignment requirement of the represented type. - pub fn align(&self) -> U32PowerOf2 { self.align } + pub fn align(&self) -> U32PowerOf2 { *self.align } /// a short name for this `TypeLayout`, useful for printing inline pub fn short_name(&self) -> String { From 6276218e26fc7ce0069a9f5db18413f0b7183e18 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:21:33 +0200 Subject: [PATCH 039/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 1134e2d..9e4a5e4 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -106,8 +106,8 @@ pub struct TypeLayout { pub kind: TypeLayoutSemantics, } -/// A `TypeLayout`, but guaranteed to be based on a `LayoutableType` and -/// a `Repr` - it follows the layout rules that correspond to the `Repr`. +/// A version of TypeLayout that provides additional compile-time guarantees. +/// It is guaranteed to represent a LayoutableType that is layed out in memory using T's layout rules. /// /// The actual `TypeLayout` can be obtained via `GpuTypeLayout::layout`. /// From e6b32b86fcc1a44022196fd724d5dc2f52cbbeb6 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:21:54 +0200 Subject: [PATCH 040/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 9e4a5e4..2e5e967 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -113,7 +113,7 @@ pub struct TypeLayout { /// /// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created /// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by -/// using `TryFrom::try_from` on a `GpuTypeLayout`, which only checks whether +/// using `TryFrom::try_from` on a `GpuTypeLayout`, which only succeeds if /// the storage layout also follows the uniform layout rules - it does not change the /// corresponding `TypeLayout`. #[derive(Debug, Clone)] From 85f82cb26ecfa945e74f11f6f687e9416aa728db Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:25:10 +0200 Subject: [PATCH 041/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 2e5e967..82b4078 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -123,7 +123,7 @@ pub struct GpuTypeLayout { } impl GpuTypeLayout { - /// Gets the `TypeLayout`. + /// Get the TypeLayout and remove compile time guarantees about the TypeRepr". pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } /// Returns the `LayoutableType` this `GpuTypeLayout` is based on. pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } From 4517791b89e9ccd7a80c9446ae804226071f09d8 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:26:36 +0200 Subject: [PATCH 042/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 82b4078..ac0e396 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -135,8 +135,10 @@ pub use repr::{TypeRepr, Repr}; pub mod repr { use super::*; - /// Type representation used by `GpuTypeLayout`. This provides guarantees - /// about the alignment rules that the type layout adheres to. + /// Implemented by marker types (such as [sm::repr::Storage] [sm::repr::Packed]), + /// which represent rulesets to lay out types in memory. + /// The user specifies these on the highest level via the #[gpu_repr(...)] attribute. + /// /// See [`GpuTypeLayout`] documentation for more details. pub trait TypeRepr: Clone + PartialEq + Eq { /// The corresponding enum variant of `Repr`. From 443d7a4f725313a790322610519bf2ccb2d0393b Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:28:38 +0200 Subject: [PATCH 043/182] . --- shame/src/frontend/rust_types/layout_traits.rs | 4 ++-- .../frontend/rust_types/type_layout/construction.rs | 2 +- shame/src/frontend/rust_types/type_layout/mod.rs | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index c5ecf3a..e8ab571 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -22,7 +22,7 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::repr::{TypeRepr, TypeReprStorageOrPacked}; +use super::type_layout::repr::{TypeRepr, DerivableRepr}; use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, GpuTypeLayout, StructLayout, TypeLayout, @@ -143,7 +143,7 @@ pub trait GpuLayout: Layoutable { /// /// `GpuRepr` only exists so a (derived) struct can be packed for use in vertex buffer usage. /// It is `repr::Storage` in all other cases. - type GpuRepr: TypeReprStorageOrPacked; + type GpuRepr: DerivableRepr; /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a /// corresponding Cpu type to the Gpu type that the derive macro is used on. diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 576f6fa..1e66134 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -133,7 +133,7 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F } } -impl GpuTypeLayout { +impl GpuTypeLayout { /// Creates a new `GpuTypeLayout` where `T` is either `Storage` or `Packed`. /// /// All LayoutableType's can be layed out according to storage and packed layout rules, diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index ac0e396..01ff900 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -129,7 +129,7 @@ impl GpuTypeLayout { pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } } -use repr::TypeReprStorageOrPacked; +use repr::DerivableRepr; pub use repr::{TypeRepr, Repr}; /// Module for all restrictions on `GpuTypeLayout`. pub mod repr { @@ -144,11 +144,10 @@ pub mod repr { /// The corresponding enum variant of `Repr`. const REPR: Repr; } - /// A subset of the types implementing `TypeRepr`. As the name suggests - /// only `Storage` and `Packed` implement this trait. - pub trait TypeReprStorageOrPacked: TypeRepr {} - impl TypeReprStorageOrPacked for Storage {} - impl TypeReprStorageOrPacked for Packed {} + /// A subset of the types implementing `TypeRepr`, which are derivable. + pub trait DerivableRepr: TypeRepr {} + impl DerivableRepr for Storage {} + impl DerivableRepr for Packed {} /// Enum of layout rules. #[derive(Debug, Clone, Copy)] From 8eeaf4660bf673b4d1e746c887dc12ae24b124ce Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:29:15 +0200 Subject: [PATCH 044/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 01ff900..527e32d 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -152,10 +152,10 @@ pub mod repr { /// Enum of layout rules. #[derive(Debug, Clone, Copy)] pub enum Repr { - /// Wgsl storage address space layout / OpenGL std430 + /// Wgsl storage address space layout /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints Storage, - /// Wgsl uniform address space layout / OpenGL std140 + /// Wgsl uniform address space layout /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints Uniform, /// Packed layout. Vertex buffer only. From 244ae458b846839971df750faa10f5f40664a246 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:33:33 +0200 Subject: [PATCH 045/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 527e32d..cc29dfa 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -243,9 +243,6 @@ pub struct ElementLayout { /// Stride of the elements pub byte_stride: u64, /// The type layout of each element in the array. - /// - /// The element layout must be constraint::Plain because it's shared by all constraints. - /// `ElementLayout` could possibly be made generic too, but it would complicate a lot. pub ty: TypeLayout, } From f09ea4c1570b8eecb46bccf04708cdc3f4ff9cf9 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 21:55:27 +0200 Subject: [PATCH 046/182] . --- .../rust_types/type_layout/construction.rs | 6 +----- .../type_layout/layoutable/align_size.rs | 15 ++++++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 1e66134..2a882d4 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -32,11 +32,7 @@ impl TypeLayout { a.align(repr), TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), ), - SizedType::Matrix(m) => ( - m.byte_size(repr, MatrixMajor::Row), - m.align(repr, MatrixMajor::Row), - TLS::Matrix(*m), - ), + SizedType::Matrix(m) => (m.byte_size(repr), m.align(repr), TLS::Matrix(*m)), SizedType::Array(a) => ( a.byte_size(repr), a.align(repr), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 7421439..22ced42 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -43,7 +43,7 @@ impl SizedType { match self { SizedType::Array(a) => a.byte_size(repr), SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(repr, MatrixMajor::Column), + SizedType::Matrix(m) => m.byte_size(repr), SizedType::Atomic(a) => a.byte_size(), SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, SizedType::Struct(s) => s.byte_size_and_align(repr).0, @@ -55,7 +55,7 @@ impl SizedType { match self { SizedType::Array(a) => a.align(repr), SizedType::Vector(v) => v.align(repr), - SizedType::Matrix(m) => m.align(repr, MatrixMajor::Column), + SizedType::Matrix(m) => m.align(repr), SizedType::Atomic(a) => a.align(repr), SizedType::PackedVec(v) => v.align(repr), SizedType::Struct(s) => s.byte_size_and_align(repr).1, @@ -249,19 +249,20 @@ pub enum MatrixMajor { #[allow(missing_docs)] impl Matrix { - pub const fn byte_size(&self, repr: Repr, major: MatrixMajor) -> u64 { - let (vec, array_len) = self.as_vector_array(major); + pub const fn byte_size(&self, repr: Repr) -> u64 { + let (vec, array_len) = self.as_vector_array(); let array_stride = array_stride(vec.align(repr), vec.byte_size()); array_size(array_stride, array_len) } - pub const fn align(&self, repr: Repr, major: MatrixMajor) -> U32PowerOf2 { - let (vec, _) = self.as_vector_array(major); + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + let (vec, _) = self.as_vector_array(); // AlignOf(vecR) vec.align(repr) } - const fn as_vector_array(&self, major: MatrixMajor) -> (Vector, NonZeroU32) { + const fn as_vector_array(&self) -> (Vector, NonZeroU32) { + let major = MatrixMajor::Column; // This can be made a parameter in the future. let (vec_len, array_len): (Len, NonZeroU32) = match major { MatrixMajor::Column => (self.rows.as_len(), self.columns.as_non_zero_u32()), MatrixMajor::Row => (self.columns.as_len(), self.rows.as_non_zero_u32()), From 33cb347573549da67ba8f258679c6dd739561d62 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:03:31 +0200 Subject: [PATCH 047/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 2a882d4..b3955b1 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -130,7 +130,7 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F } impl GpuTypeLayout { - /// Creates a new `GpuTypeLayout` where `T` is either `Storage` or `Packed`. + /// Creates a new `GpuTypeLayout` where `T` implements [`DerivableRepr`]. /// /// All LayoutableType's can be layed out according to storage and packed layout rules, /// which corresponds to the available #[gpu_repr(packed)] and #[gpu_repr(storage)] From ee4c23d1ac14a566126bcd2f6934ef88d4228bf6 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:04:05 +0200 Subject: [PATCH 048/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index b3955b1..21d1dae 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -150,7 +150,7 @@ impl TryFrom> for GpuTypeLayout { type Error = LayoutError; fn try_from(ty: GpuTypeLayout) -> Result { - check_layout(ty.layoutable_type(), Repr::Storage, Repr::Uniform)?; + check_repr_equivalence_for_type(ty.layoutable_type(), Repr::Storage, Repr::Uniform)?; Ok(GpuTypeLayout { ty: ty.ty, _repr: PhantomData, @@ -163,7 +163,11 @@ impl TryFrom> for GpuTypeLayout { /// /// Another way to say this is, that we are laying `ty` out according to two different /// layout rules and checking whether the byte representation of those layouts is the same. -fn check_layout(ty: &LayoutableType, actual_repr: Repr, expected_repr: Repr) -> Result<(), LayoutError> { +fn check_repr_equivalence_for_type( + ty: &LayoutableType, + actual_repr: Repr, + expected_repr: Repr, +) -> Result<(), LayoutError> { let ctx = LayoutContext { top_level_type: ty, actual_repr, From 0f90123cb86630bf7fa1e375fe09a5a28e1a7671 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:29:06 +0200 Subject: [PATCH 049/182] . --- .../src/frontend/rust_types/type_layout/layoutable/align_size.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 22ced42..cef9df7 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -167,6 +167,7 @@ impl<'a> FieldOffsetsUnsized<'a> { /// Returns the last field's offset and the struct's align. pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { // Finishing layout calculations + // using count only to advance iterator to the end (&mut self.sized).count(); let array_align = self.last_unsized.array.align(self.sized.repr); let custom_min_align = self.last_unsized.custom_min_align; From 622fe48b26aef79677cb9a736904cb21fb7c82e2 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:29:30 +0200 Subject: [PATCH 050/182] . --- .../src/frontend/rust_types/type_layout/layoutable/align_size.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index cef9df7..36259d4 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -135,6 +135,7 @@ impl<'a> FieldOffsetsSized<'a> { /// with exactly the sized fields that this FieldOffsets was created with. pub fn struct_byte_size_and_align(mut self) -> (u64, U32PowerOf2) { // Finishing layout calculations + // using count only to advance iterator to the end (&mut self.0).count(); (self.0.calc.byte_size(), struct_align(self.0.calc.align(), self.0.repr)) } From 43afa0f28458afb5ccdf91f615c54802ae55e2d3 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:30:00 +0200 Subject: [PATCH 051/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 2 +- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 21d1dae..2f16cbc 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -516,7 +516,7 @@ where }; let (struct_name, sized_fields, mut field_offsets) = match struct_type { - StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr).into_sized_fields()), + StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr).into_inner()), StructKind::Unsized(s) => ( &s.name, s.sized_fields.as_slice(), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 36259d4..b2aef02 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -141,7 +141,7 @@ impl<'a> FieldOffsetsSized<'a> { } /// Returns the inner iterator over sized fields. - pub fn into_sized_fields(self) -> FieldOffsets<'a> { self.0 } + pub fn into_inner(self) -> FieldOffsets<'a> { self.0 } } /// The field offsets of an `UnsizedStruct`. From 86d4530404064e4c07f7bc69342a0a1933c97bc6 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:42:41 +0200 Subject: [PATCH 052/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index b2aef02..5faff4c 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -210,8 +210,9 @@ impl Vector { pub const fn byte_size(&self) -> u64 { self.len.as_u64() * self.scalar.byte_size() } pub const fn align(&self, repr: Repr) -> U32PowerOf2 { - if repr.is_packed() { - return PACKED_ALIGN; + match repr { + Repr::Packed => return PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} } let len = match self.len { From 8c8be8d3256f12ed646422973b52ecca94f27875 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:45:04 +0200 Subject: [PATCH 053/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 5faff4c..668cef8 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -219,8 +219,8 @@ impl Vector { Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), Len::X3 => 4, }; - // len * self.scalar.align() = power of 2 * power of 2 = power of 2 - U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()).unwrap() + U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()) + .expect("power of 2 * power of 2 = power of 2. Highest operands are around 4 * 16 so overflow is unlikely") } } From 26b658f92f46fbf0e8d7f4e0529bc6c918dd7284 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:45:59 +0200 Subject: [PATCH 054/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 668cef8..0cc488d 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -318,7 +318,7 @@ pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 } } -/// Returns an array's size given the alignment and size of it's elements. +/// Returns an array's size=>stride (the distance between consecutive elements) given the alignment and size of its elements. pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { // Arrays of element type T must have an element stride that is a multiple of the // RequiredAlignOf(T, C) for the address space C: From c115a7df5e9250e49bb79a28c2ad92c9ea7c6761 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:47:07 +0200 Subject: [PATCH 055/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 0cc488d..70f57e9 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -374,7 +374,7 @@ pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32Powe /// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. /// -/// If `LayoutCalculator` is created with `packed = true`, provided `field_align`s +/// If `LayoutCalculator` is created with `repr == Repr::Packed`, provided `field_align`s /// are ignored and the field is inserted directly after the previous field. However, /// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. #[derive(Debug, Clone)] From 9bd22b0f5860072b246dad98bf78d8114b20bf5f Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:48:13 +0200 Subject: [PATCH 056/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 70f57e9..c490056 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -409,8 +409,9 @@ impl LayoutCalculator { is_struct: bool, ) -> u64 { // Just in case the user didn't already do this. - if self.repr.is_packed() { - field_align = PACKED_ALIGN; + match self.repr { + Repr::Packed => field_align = PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} } let size = Self::calculate_byte_size(field_size, custom_min_size); From a7ecc3826286de31e2a524669ad1ba672a04153a Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:49:15 +0200 Subject: [PATCH 057/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index c490056..114a068 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -424,7 +424,7 @@ impl LayoutCalculator { // bytes between the start of that member and the start of any following // member must be at least roundUp(16, SizeOf(S)). (Repr::Uniform, true) => round_up(16, offset + size), - _ => offset + size, + (Repr::Storage | Repr::Packed, _) | (Repr::Uniform, false) => offset + size, }; self.align = self.align.max(align); From 0fdc3979a3d7217c1dbca15403e8ab776f69692e Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:49:47 +0200 Subject: [PATCH 058/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 114a068..b2497b6 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -443,8 +443,9 @@ impl LayoutCalculator { custom_min_align: Option, ) -> (u64, U32PowerOf2) { // Just in case the user didn't already do this. - if self.repr.is_packed() { - field_align = PACKED_ALIGN; + match self.repr { + Repr::Packed => field_align = PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} } let align = Self::calculate_align(field_align, custom_min_align); From b64431553d2611b4d42eece6b6f0749490385d15 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:51:32 +0200 Subject: [PATCH 059/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index b2497b6..43443f3 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -467,9 +467,8 @@ impl LayoutCalculator { /// Returns the align of the struct. pub const fn align(&self) -> U32PowerOf2 { self.align } - /// field_align should already respect field_custom_min_align. - /// field_custom_min_align is used to overwrite packing if self is packed. const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { + let field_align = Self::calculate_align(field_align, field_custom_min_align); match (self.repr, field_custom_min_align) { (Repr::Packed, None) => self.next_offset_min, (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), From c8ade9540685a3c5b307eab45f1129602e0c189b Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 29 Jun 2025 22:52:09 +0200 Subject: [PATCH 060/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 43443f3..f795129 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -472,7 +472,7 @@ impl LayoutCalculator { match (self.repr, field_custom_min_align) { (Repr::Packed, None) => self.next_offset_min, (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), - (_, _) => round_up(field_align.as_u64(), self.next_offset_min), + (Repr::Storage | Repr::Uniform, _) => round_up(field_align.as_u64(), self.next_offset_min), } } From 1ecb22364c2da0858dd2a51946e34ce1382555b5 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:20:01 +0200 Subject: [PATCH 061/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index 399999f..e4b2796 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -188,7 +188,7 @@ impl RuntimeSizedArrayField { } impl SizedArray { - /// Creates a new `RuntimeSizedArray` from it's element type and length. + /// Creates a new `SizedArray` from it's element type and length. pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { Self { element: Rc::new(element_ty.into()), From 7d5426db02b706882112fcd411f9c4a72d9011f2 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:21:52 +0200 Subject: [PATCH 062/182] . --- shame/src/frontend/rust_types/array.rs | 4 ++-- .../src/frontend/rust_types/type_layout/layoutable/builder.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 3ddbac7..faebced 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -158,13 +158,13 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoBools for Array {} impl LayoutableSized for Array> { fn layoutable_type_sized() -> layoutable::SizedType { - layoutable::SizedArray::new(T::layoutable_type_sized(), Size::::nonzero()).into() + layoutable::SizedArray::new(Rc::new(T::layoutable_type_sized()), Size::::nonzero()).into() } } impl Layoutable for Array { fn layoutable_type() -> layoutable::LayoutableType { match N::LEN { - Some(n) => layoutable::SizedArray::new(T::layoutable_type_sized(), n).into(), + Some(n) => layoutable::SizedArray::new(Rc::new(T::layoutable_type_sized()), n).into(), None => layoutable::RuntimeSizedArray::new(T::layoutable_type_sized()).into(), } } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index e4b2796..86bdb38 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -189,9 +189,9 @@ impl RuntimeSizedArrayField { impl SizedArray { /// Creates a new `SizedArray` from it's element type and length. - pub fn new(element_ty: impl Into, len: NonZeroU32) -> Self { + pub fn new(element_ty: Rc, len: NonZeroU32) -> Self { Self { - element: Rc::new(element_ty.into()), + element: element_ty, len, } } From 41ae4a66a18e47b1e48656692a66cf5e90a247e4 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:23:38 +0200 Subject: [PATCH 063/182] . --- .../type_layout/layoutable/builder.rs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index 86bdb38..df7113a 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -206,27 +206,6 @@ impl RuntimeSizedArray { } } -impl> From<(T, SizedType)> for SizedField { - fn from(value: (T, SizedType)) -> Self { - Self { - name: value.0.into(), - ty: value.1, - custom_min_size: None, - custom_min_align: None, - } - } -} - -impl> From<(T, SizedType)> for RuntimeSizedArrayField { - fn from(value: (T, SizedType)) -> Self { - Self { - name: value.0.into(), - array: RuntimeSizedArray { element: value.1 }, - custom_min_align: None, - } - } -} - /// Options for the field of a struct. /// /// If you only want to customize the field's name, you can convert most string types From b9bae54acc8ef38247f5b20c41fd42f8802a76d0 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:25:24 +0200 Subject: [PATCH 064/182] . --- .../src/frontend/rust_types/type_layout/layoutable/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index df7113a..6f04b78 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -209,8 +209,8 @@ impl RuntimeSizedArray { /// Options for the field of a struct. /// /// If you only want to customize the field's name, you can convert most string types -/// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, -/// meaning you can just pass the string type directly. +/// to `FieldOptions` using `Into::into`. For most methods that take `impl Into` +/// parameters you can just pass the string type directly. #[derive(Debug, Clone)] pub struct FieldOptions { /// Name of the field From ba5e5345e9cd9775ffdc38e8813b141650ee12a1 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:29:10 +0200 Subject: [PATCH 065/182] . --- .../frontend/rust_types/type_layout/layoutable/builder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index 6f04b78..ea6e07f 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -209,7 +209,7 @@ impl RuntimeSizedArray { /// Options for the field of a struct. /// /// If you only want to customize the field's name, you can convert most string types -/// to `FieldOptions` using `Into::into`. For most methods that take `impl Into` +/// to `FieldOptions` using `Into::into`. For methods that take `impl Into` /// parameters you can just pass the string type directly. #[derive(Debug, Clone)] pub struct FieldOptions { @@ -225,8 +225,8 @@ impl FieldOptions { /// Creates new `FieldOptions`. /// /// If you only want to customize the field's name, you can convert most string types - /// to `FieldOptions` using `Into::into`, but most methods take `impl Into`, - /// meaning you can just pass the string type directly. + /// to `FieldOptions` using `Into::into`. For methods that take `impl Into` + /// parameters you can just pass the string type directly. pub fn new( name: impl Into, custom_min_align: Option, From 4ce2fdc08e278f8f1453acc98bbab058b57fe2ef Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:31:26 +0200 Subject: [PATCH 066/182] . --- .../frontend/rust_types/type_layout/layoutable/ir_compat.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 81fd987..0c86575 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -6,10 +6,7 @@ use super::*; #[derive(thiserror::Error, Debug)] pub enum IRConversionError { /// Packed vectors do not exist in the shader type system. - #[error( - "Type is or contains a packed vector, which does not exist in the shader type system.\n\ - Packed vectors may only be used in vertex buffers." - )] + #[error("Type is or contains a packed vector, which does not exist in the shader type system.")] ContainsPackedVector, /// Struct field names must be unique in the shader type system. #[error("{0}")] From 6d15f81fa393efc87a983e611ee4d6de695dc7ee Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:31:51 +0200 Subject: [PATCH 067/182] . --- .../type_layout/layoutable/ir_compat.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 0c86575..13096df 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -16,8 +16,8 @@ pub enum IRConversionError { #[derive(Debug)] pub struct DuplicateFieldNameError { pub struct_type: StructKind, - pub first_field: usize, - pub second_field: usize, + pub first_occurence: usize, + pub second_occurence: usize, pub use_color: bool, } @@ -31,7 +31,7 @@ impl std::fmt::Display for DuplicateFieldNameError { }; let indent = " "; - let is_duplicate = |i| self.first_field == i || self.second_field == i; + let is_duplicate = |i| self.first_occurence == i || self.second_occurence == i; let arrow = |i| match is_duplicate(i) { true => " <--", false => "", @@ -152,8 +152,8 @@ impl TryFrom for ir::ir_type::SizedStruct { if let Some((first, second)) = check_for_duplicate_field_names(ty.fields(), None) { return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { struct_type: StructKind::Sized(ty), - first_field: first, - second_field: second, + first_occurence: first, + second_occurence: second, use_color: use_color(), })); } @@ -178,8 +178,8 @@ impl TryFrom for ir::ir_type::BufferBlock { if let Some((first, second)) = check_for_duplicate_field_names(&ty.sized_fields, Some(&ty.last_unsized)) { return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { struct_type: StructKind::Unsized(ty), - first_field: first, - second_field: second, + first_occurence: first, + second_occurence: second, use_color: use_color(), })); } @@ -384,8 +384,8 @@ fn test_ir_conversion_error() { result, Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { struct_type: StructKind::Sized(_), - first_field: 0, - second_field: 2, + first_occurence: 0, + second_occurence: 2, .. })) )); From d3c039a1f996d7935ae7973272b911197f7d8ac9 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:37:51 +0200 Subject: [PATCH 068/182] . --- .../frontend/rust_types/type_layout/layoutable/ir_compat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 13096df..7f3c12c 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -83,9 +83,9 @@ fn check_for_duplicate_field_names( // we'd usually deal with. let mut duplicate_fields = None; for (i, field1) in sized_fields.iter().enumerate() { - for (j, field2) in sized_fields.iter().skip(i + 1).enumerate() { + for (j, field2) in sized_fields.iter().enumerate().skip(i + 1) { if field1.name == field2.name { - duplicate_fields = Some((i, i + 1 + j)); + duplicate_fields = Some((i, j)); break; } } From 863078d958dd9fc522e8004cffe400c021b0b7be Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:38:31 +0200 Subject: [PATCH 069/182] . --- .../rust_types/type_layout/layoutable/ir_compat.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 7f3c12c..bb1bb40 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -100,7 +100,9 @@ fn check_for_duplicate_field_names( } #[track_caller] -fn use_color() -> bool { Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false) } +fn should_use_color() -> bool { + Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false) +} impl TryFrom for ir::StoreType { type Error = IRConversionError; @@ -154,7 +156,7 @@ impl TryFrom for ir::ir_type::SizedStruct { struct_type: StructKind::Sized(ty), first_occurence: first, second_occurence: second, - use_color: use_color(), + use_color: should_use_color(), })); } @@ -180,7 +182,7 @@ impl TryFrom for ir::ir_type::BufferBlock { struct_type: StructKind::Unsized(ty), first_occurence: first, second_occurence: second, - use_color: use_color(), + use_color: should_use_color(), })); } From 1f0844b7e6c62abf9da2777735b84787b6924f33 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 00:56:26 +0200 Subject: [PATCH 070/182] . --- .../type_layout/layoutable/ir_compat.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index bb1bb40..e6eecfb 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -147,6 +147,13 @@ impl From for ir::ScalarType { } } +impl SizedStruct { + fn into_parts_split_last(mut self) -> (CanonName, SizedField, Vec) { + let last = self.fields.pop().expect("guaranteed to have at least one field"); + (self.name, last, self.fields) + } +} + impl TryFrom for ir::ir_type::SizedStruct { type Error = IRConversionError; @@ -160,13 +167,11 @@ impl TryFrom for ir::ir_type::SizedStruct { })); } - let mut fields: Vec = Vec::new(); - for field in ty.fields { - fields.push(field.try_into()?); - } - let last_field = fields.pop().unwrap(); + let (name, last_field, first_fields) = ty.into_parts_split_last(); + let first_fields: Result, _> = first_fields.into_iter().map(|f| f.try_into()).collect(); + let last_field_ir = last_field.try_into()?; - match ir::ir_type::SizedStruct::new_nonempty(ty.name, fields, last_field) { + match ir::ir_type::SizedStruct::new_nonempty(name, first_fields?, last_field_ir) { Ok(s) => Ok(s), Err(StructureFieldNamesMustBeUnique) => unreachable!("checked above"), } From bc3770ad90223979a4cd9fd4feb37a82dcc974aa Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:51:13 +0200 Subject: [PATCH 071/182] detailed duplicate field fame check moved to ir::SizedStruct --- .../src/frontend/rust_types/layout_traits.rs | 6 +- .../type_layout/layoutable/ir_compat.rs | 84 +++++++------------ shame/src/ir/ir_type/struct_.rs | 55 +++++++++--- shame/src/ir/pipeline/wip_pipeline.rs | 2 +- shame/src/ir/recording/builtin_templates.rs | 6 +- shame_derive/src/derive_layout.rs | 4 +- 6 files changed, 82 insertions(+), 75 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index e8ab571..957ddc9 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -437,7 +437,7 @@ where ); match struct_ { Ok(s) => s, - Err(ir::StructureFieldNamesMustBeUnique) => unreachable!("field names are assumed unique"), + Err(ir::StructureFieldNamesMustBeUnique { .. }) => unreachable!("field names are assumed unique"), } } } @@ -531,7 +531,9 @@ impl BufferFields for GpuT { Ok(t) => t, Err(e) => match e { E::MustHaveAtLeastOneField => unreachable!(">= 1 field is ensured by derive macro"), - E::FieldNamesMustBeUnique => unreachable!("unique field idents are ensured by rust struct definition"), + E::FieldNamesMustBeUnique(_) => { + unreachable!("unique field idents are ensured by rust struct definition") + } }, } } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index e6eecfb..25b7964 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -75,30 +75,6 @@ impl std::fmt::Display for DuplicateFieldNameError { } } -fn check_for_duplicate_field_names( - sized_fields: &[SizedField], - last_unsized: Option<&RuntimeSizedArrayField>, -) -> Option<(usize, usize)> { - // Brute force search > HashMap for the amount of fields - // we'd usually deal with. - let mut duplicate_fields = None; - for (i, field1) in sized_fields.iter().enumerate() { - for (j, field2) in sized_fields.iter().enumerate().skip(i + 1) { - if field1.name == field2.name { - duplicate_fields = Some((i, j)); - break; - } - } - if let Some(last_unsized) = last_unsized { - if field1.name == last_unsized.name { - duplicate_fields = Some((i, sized_fields.len())); - break; - } - } - } - duplicate_fields -} - #[track_caller] fn should_use_color() -> bool { Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false) @@ -148,9 +124,8 @@ impl From for ir::ScalarType { } impl SizedStruct { - fn into_parts_split_last(mut self) -> (CanonName, SizedField, Vec) { - let last = self.fields.pop().expect("guaranteed to have at least one field"); - (self.name, last, self.fields) + fn fields_split_last(&self) -> (&SizedField, &[SizedField]) { + self.fields.split_last().expect("guaranteed to have at least one field") } } @@ -158,22 +133,21 @@ impl TryFrom for ir::ir_type::SizedStruct { type Error = IRConversionError; fn try_from(ty: SizedStruct) -> Result { - if let Some((first, second)) = check_for_duplicate_field_names(ty.fields(), None) { - return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { - struct_type: StructKind::Sized(ty), - first_occurence: first, - second_occurence: second, - use_color: should_use_color(), - })); - } + let (last_field, first_fields) = ty.fields_split_last(); + let first_fields: Result, _> = first_fields.into_iter().map(|f| f.clone().try_into()).collect(); + let last_field_ir = last_field.clone().try_into()?; - let (name, last_field, first_fields) = ty.into_parts_split_last(); - let first_fields: Result, _> = first_fields.into_iter().map(|f| f.try_into()).collect(); - let last_field_ir = last_field.try_into()?; - - match ir::ir_type::SizedStruct::new_nonempty(name, first_fields?, last_field_ir) { + match ir::ir_type::SizedStruct::new_nonempty(ty.name.clone(), first_fields?, last_field_ir) { Ok(s) => Ok(s), - Err(StructureFieldNamesMustBeUnique) => unreachable!("checked above"), + Err(StructureFieldNamesMustBeUnique { + first_occurence, + second_occurence, + }) => Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Sized(ty), + first_occurence, + second_occurence, + use_color: should_use_color(), + })), } } } @@ -182,26 +156,24 @@ impl TryFrom for ir::ir_type::BufferBlock { type Error = IRConversionError; fn try_from(ty: UnsizedStruct) -> Result { - if let Some((first, second)) = check_for_duplicate_field_names(&ty.sized_fields, Some(&ty.last_unsized)) { - return Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { - struct_type: StructKind::Unsized(ty), - first_occurence: first, - second_occurence: second, - use_color: should_use_color(), - })); - } - let mut sized_fields: Vec = Vec::new(); - for field in ty.sized_fields { - sized_fields.push(field.try_into()?); + for field in ty.sized_fields.iter() { + sized_fields.push(field.clone().try_into()?); } + let last_unsized = ty.last_unsized.clone().try_into()?; - let last_unsized = ty.last_unsized.try_into()?; - - match ir::ir_type::BufferBlock::new(ty.name, sized_fields, Some(last_unsized)) { + match ir::ir_type::BufferBlock::new(ty.name.clone(), sized_fields, Some(last_unsized)) { Ok(b) => Ok(b), - Err(BufferBlockDefinitionError::FieldNamesMustBeUnique) => unreachable!("checked above"), + Err(BufferBlockDefinitionError::FieldNamesMustBeUnique(StructureFieldNamesMustBeUnique { + first_occurence, + second_occurence, + })) => Err(IRConversionError::DuplicateFieldName(DuplicateFieldNameError { + struct_type: StructKind::Unsized(ty), + first_occurence, + second_occurence, + use_color: should_use_color(), + })), Err(BufferBlockDefinitionError::MustHaveAtLeastOneField) => { unreachable!("last_unsized is at least one field") } diff --git a/shame/src/ir/ir_type/struct_.rs b/shame/src/ir/ir_type/struct_.rs index fbfc9f1..3f1a574 100644 --- a/shame/src/ir/ir_type/struct_.rs +++ b/shame/src/ir/ir_type/struct_.rs @@ -42,7 +42,7 @@ pub enum StructureDefinitionError { )] RuntimeSizedArrayNotAllowedInSizedStruct, #[error("field names must be unique within a structure definition")] - FieldNamesMustBeUnique, + FieldNamesMustBeUnique(StructureFieldNamesMustBeUnique), } pub trait Field { @@ -164,10 +164,7 @@ impl Struct { first_sized_fields.push(last_sized_field); let sized_fields = first_sized_fields; assert!(!sized_fields.is_empty()); - use crate::common::iterator_ext::IteratorExt; - if !sized_fields.iter().all_unique_by(|a, b| a.name == b.name) { - return Err(StructureFieldNamesMustBeUnique); - } + check_for_duplicate_field_names(&sized_fields, None)?; let struct_ = Rc::new(Self { kind: StructKind::Sized, name, @@ -210,9 +207,10 @@ impl Struct { ctx.latest_user_caller(), ); }); - if !struct_.fields().all_unique_by(|a, b| a.name() == b.name()) { - return Err(StructureDefinitionError::FieldNamesMustBeUnique); + if let Err(e) = check_for_duplicate_field_names(&struct_.sized_fields, struct_.last_unsized.as_ref()) { + return Err(StructureDefinitionError::FieldNamesMustBeUnique(e)); } + Ok(struct_) } @@ -426,7 +424,42 @@ impl TryFrom> for BufferBlock { } /// an error created if a struct contains two or more fields of the same name -pub struct StructureFieldNamesMustBeUnique; +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructureFieldNamesMustBeUnique { + pub first_occurence: usize, + pub second_occurence: usize, +} + +fn check_for_duplicate_field_names( + sized_fields: &[SizedField], + last_unsized: Option<&RuntimeSizedArrayField>, +) -> Result<(), StructureFieldNamesMustBeUnique> { + // Brute force search > HashMap for the amount of fields + // we'd usually deal with. + let mut duplicate_fields = None; + for (i, field1) in sized_fields.iter().enumerate() { + for (j, field2) in sized_fields.iter().enumerate().skip(i + 1) { + if field1.name == field2.name { + duplicate_fields = Some((i, j)); + break; + } + } + if let Some(last_unsized) = last_unsized { + if field1.name == last_unsized.name { + duplicate_fields = Some((i, sized_fields.len())); + break; + } + } + } + match duplicate_fields { + Some((first_occurence, second_occurence)) => Err(StructureFieldNamesMustBeUnique { + first_occurence, + second_occurence, + }), + None => Ok(()), + } +} impl SizedStruct { #[track_caller] @@ -467,12 +500,12 @@ impl SizedStruct { } #[allow(missing_docs)] -#[derive(Error, Debug, Clone, Copy)] +#[derive(Error, Debug, Clone)] pub enum BufferBlockDefinitionError { #[error("buffer block must have at least one field")] MustHaveAtLeastOneField, #[error("field names of a buffer block must be unique")] - FieldNamesMustBeUnique, + FieldNamesMustBeUnique(StructureFieldNamesMustBeUnique), } impl BufferBlock { @@ -489,7 +522,7 @@ impl BufferBlock { E::WrongStructKindForStructType(_, _) => unreachable!("error never created by Struct::new"), E::RuntimeSizedArrayNotAllowedInSizedStruct => unreachable!("not a sized struct"), E::MustHaveAtLeastOneField(_) => BufferBlockDefinitionError::MustHaveAtLeastOneField, - E::FieldNamesMustBeUnique => BufferBlockDefinitionError::FieldNamesMustBeUnique, + E::FieldNamesMustBeUnique(e) => BufferBlockDefinitionError::FieldNamesMustBeUnique(e), }), } } diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 64043f6..3aa5adb 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -420,7 +420,7 @@ impl WipPushConstantsField { fields.iter().map(&mut to_sized_field).collect(), to_sized_field(last) ).map_err(|err| match err { - StructureFieldNamesMustBeUnique => { + StructureFieldNamesMustBeUnique { .. } => { InternalError::new(true, format!("intermediate push constants structure field names are not unique. fields: {fields:?}, last: {last:?}")) } }) diff --git a/shame/src/ir/recording/builtin_templates.rs b/shame/src/ir/recording/builtin_templates.rs index 9eca60b..682d923 100644 --- a/shame/src/ir/recording/builtin_templates.rs +++ b/shame/src/ir/recording/builtin_templates.rs @@ -156,7 +156,7 @@ impl BuiltinTemplateStructs { new_field("exp", SizedType::Vector(len, ir::ScalarType::I32)), ); match struc { - Err(StructureFieldNamesMustBeUnique) => unreachable!("field names above are unique"), + Err(StructureFieldNamesMustBeUnique { .. }) => unreachable!("field names above are unique"), Ok(s) => s, } } @@ -167,7 +167,7 @@ impl BuiltinTemplateStructs { new_field("whole", SizedType::Vector(len, fp.into())), ); match struc { - Err(StructureFieldNamesMustBeUnique) => unreachable!("field names above are unique"), + Err(StructureFieldNamesMustBeUnique { .. }) => unreachable!("field names above are unique"), Ok(s) => s, } } @@ -179,7 +179,7 @@ impl BuiltinTemplateStructs { new_field("exchanged", SizedType::Vector(ir::Len::X1, ir::ScalarType::Bool)), ); match struc { - Err(StructureFieldNamesMustBeUnique) => unreachable!("field names above are unique"), + Err(StructureFieldNamesMustBeUnique { .. }) => unreachable!("field names above are unique"), Ok(s) => s, } } diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 304e82f..c1e022d 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -464,7 +464,7 @@ pub fn impl_for_struct( Ok(t) => t, Err(e) => match e { E::MustHaveAtLeastOneField => unreachable!(">= 1 field is ensured by derive macro"), - E::FieldNamesMustBeUnique => unreachable!("unique field idents are ensured by rust struct definition"), + E::FieldNamesMustBeUnique(_) => unreachable!("unique field idents are ensured by rust struct definition"), } } } @@ -536,7 +536,7 @@ pub fn impl_for_struct( ); match struct_ { Ok(s) => s, - Err(#re::ir::StructureFieldNamesMustBeUnique) => unreachable!("field name uniqueness is checked by rust"), + Err(#re::ir::StructureFieldNamesMustBeUnique { .. }) => unreachable!("field name uniqueness is checked by rust"), } } } From 1250b70ad53d66f3145f5fa1a545c775df18523a Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:55:58 +0200 Subject: [PATCH 072/182] . --- .../src/frontend/rust_types/type_layout/layoutable/ir_compat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 25b7964..0ec567b 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -222,7 +222,7 @@ impl TryFrom for ir::ir_type::RuntimeSizedArrayField { #[error("Type contains bools, which doesn't have a known layout.")] pub struct ContainsBoolsError; -/// Errors that can occur when converting IR types to layoutable types. +/// Errors that can occur when converting IR types to layoutable types on the gpu. #[derive(thiserror::Error, Debug)] pub enum LayoutableConversionError { /// Type contains bools, which don't have a standardized memory layout. From 857f026f5f3ae13ffc6a80b7415aadca5fce3aed Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:56:20 +0200 Subject: [PATCH 073/182] . --- .../src/frontend/rust_types/type_layout/layoutable/ir_compat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 0ec567b..bddcd44 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -274,7 +274,7 @@ impl TryFrom for SizedStruct { fn try_from(structure: ir::ir_type::SizedStruct) -> Result { let mut fields = Vec::new(); - for field in structure.sized_fields() { + for field in structure.fields() { fields.push(SizedField { name: field.name.clone(), custom_min_size: field.custom_min_size, From 77811eeeeaf67abc35e4d0b2350e88b555ab97c9 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:57:21 +0200 Subject: [PATCH 074/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index f795129..c94cc03 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -431,7 +431,7 @@ impl LayoutCalculator { offset } - /// Extends the layout by an runtime sized array field given it's align. + /// Extends the layout by a runtime sized array field given it's align. /// /// Returns (last field offset, align) /// From 7c9cee0ed4e1ed14d5842032a44da5e08da38d57 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:57:57 +0200 Subject: [PATCH 075/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 0bbf272..80ad5a3 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -237,7 +237,7 @@ impl std::fmt::Display for SizedType { } impl std::fmt::Display for Vector { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}x{}", self.scalar, self.len) } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}", self.scalar, self.len) } } impl std::fmt::Display for Matrix { From 90fccaddf8c24f0ad63cf0a5942d6b31306d3700 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:58:23 +0200 Subject: [PATCH 076/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 80ad5a3..66f4619 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -103,7 +103,7 @@ pub struct SizedStruct { fields: Vec, } -/// A struct whose size is not known at compile time. +/// A struct whose size is not known before shader runtime. /// /// This struct has a runtime sized array as it's last field. #[derive(Debug, Clone)] From ea1d2f1b925396b2f968c37ee7947eb18efb54a2 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:59:01 +0200 Subject: [PATCH 077/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 66f4619..c4bfeac 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -26,7 +26,7 @@ pub use builder::{SizedOrArray, FieldOptions}; /// storage, uniform or packed. #[derive(Debug, Clone)] pub enum LayoutableType { - /// A type with a statically known size. + /// A type with a known size. Sized(SizedType), /// A struct with a runtime sized array as it's last field. UnsizedStruct(UnsizedStruct), From 401116c82b9300e5cdb7fa6697c400cf58056005 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 01:59:38 +0200 Subject: [PATCH 078/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index c4bfeac..0d464c1 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -19,7 +19,7 @@ pub(crate) mod ir_compat; pub use align_size::{FieldOffsets, MatrixMajor, LayoutCalculator, array_size, array_stride, array_align}; pub use builder::{SizedOrArray, FieldOptions}; -/// Types that have a defined memory layout. +/// Types that can be layed out in memory. /// /// `LayoutableType` does not contain any layout information itself, but a layout /// can be assigned to it using [`GpuTypeLayout`] according to one of the available layout rules: From c9181ac0dc3756137c914f8413545d29d5ceded3 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:01:33 +0200 Subject: [PATCH 079/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 0d464c1..1570914 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -34,7 +34,7 @@ pub enum LayoutableType { RuntimeSizedArray(RuntimeSizedArray), } -/// Types with a statically known size. +/// Types that have a size which is known at shader creation time. #[allow(missing_docs)] #[derive(Debug, Clone)] pub enum SizedType { From 8ef3f0467633e1a240e139a9302f28c102442373 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:02:24 +0200 Subject: [PATCH 080/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index c94cc03..61fa4fb 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -24,7 +24,7 @@ impl LayoutableType { } } - /// This is expensive for structs as it calculates the byte size and align from scratch. + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. pub fn byte_size_and_align(&self, repr: Repr) -> (Option, U32PowerOf2) { match self { LayoutableType::Sized(s) => { From cd8c5c0a1babf572ce7fd356a28eecb1b852524f Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:08:46 +0200 Subject: [PATCH 081/182] . --- .../rust_types/type_layout/layoutable/align_size.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 61fa4fb..0d10f20 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -73,9 +73,9 @@ impl SizedType { impl SizedStruct { - /// Returns [`FieldOffsets`], which serves as an iterator over the offsets of the - /// fields of this struct. `SizedStruct::byte_size_and_align_from_offsets` can be - /// used to efficiently obtain the byte_size + /// Returns [`FieldOffsetsSized`], which serves as an iterator over the offsets of the + /// fields of this struct. `FieldOffsetsSized::struct_byte_size_and_align` can be + /// used to efficiently obtain the byte_size and align. pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsSized { FieldOffsetsSized(FieldOffsets::new(self.fields(), repr)) } From 98fbad5ddfcc5c2fb5e54676b24d7a79a8645871 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:11:18 +0200 Subject: [PATCH 082/182] . --- .../rust_types/type_layout/layoutable/align_size.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 0d10f20..e2adad7 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -62,7 +62,7 @@ impl SizedType { } } - /// This is expensive for structs as it calculates the byte size and align from scratch. + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { match self { SizedType::Struct(s) => s.byte_size_and_align(repr), @@ -82,10 +82,7 @@ impl SizedStruct { /// Returns (byte_size, align) /// - /// ### Careful! - /// This is an expensive operation as it calculates byte size and align from scratch. - /// If you also need field offsets, use [`SizedStruct::field_offsets`] instead and - /// read the documentation of [`FieldOffsets`] on how to obtain the byte size and align from it. + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { self.field_offsets(repr).struct_byte_size_and_align() } From 9c3c9a13d21bf05218d0f3a0b791e6dbed0ae81e Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:13:12 +0200 Subject: [PATCH 083/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 6 +----- .../rust_types/type_layout/layoutable/align_size.rs | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 2f16cbc..31d5cae 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -517,11 +517,7 @@ where let (struct_name, sized_fields, mut field_offsets) = match struct_type { StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr).into_inner()), - StructKind::Unsized(s) => ( - &s.name, - s.sized_fields.as_slice(), - s.field_offsets(repr).into_sized_fields(), - ), + StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), s.field_offsets(repr).into_inner()), }; let indent = " "; diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index e2adad7..cc87dac 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -174,7 +174,7 @@ impl<'a> FieldOffsetsUnsized<'a> { } /// Returns the inner iterator over sized fields. - pub fn into_sized_fields(self) -> FieldOffsets<'a> { self.sized } + pub fn into_inner(self) -> FieldOffsets<'a> { self.sized } } impl UnsizedStruct { @@ -187,7 +187,7 @@ impl UnsizedStruct { FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, repr) } - /// This is expensive as it calculates the byte align from scratch. + /// This is expensive as it calculates the byte align by traversing all fields recursively. pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } } From 5d6d7f64f95969b92e28e53c501278e5a5abedcb Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:13:41 +0200 Subject: [PATCH 084/182] . --- .../rust_types/type_layout/layoutable/align_size.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index cc87dac..2284481 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -134,7 +134,10 @@ impl<'a> FieldOffsetsSized<'a> { // Finishing layout calculations // using count only to advance iterator to the end (&mut self.0).count(); - (self.0.calc.byte_size(), struct_align(self.0.calc.align(), self.0.repr)) + ( + self.0.calc.byte_size(), + adjust_struct_alignment_for_repr(self.0.calc.align(), self.0.repr), + ) } /// Returns the inner iterator over sized fields. @@ -170,7 +173,7 @@ impl<'a> FieldOffsetsUnsized<'a> { let array_align = self.last_unsized.array.align(self.sized.repr); let custom_min_align = self.last_unsized.custom_min_align; let (offset, align) = self.sized.calc.extend_unsized(array_align, custom_min_align); - (offset, struct_align(align, self.sized.repr)) + (offset, adjust_struct_alignment_for_repr(align, self.sized.repr)) } /// Returns the inner iterator over sized fields. @@ -191,7 +194,7 @@ impl UnsizedStruct { pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } } -const fn struct_align(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { +const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. Repr::Storage => align, From 880bb133a85517c6a71f6db7e5ce1af459ab9652 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:15:48 +0200 Subject: [PATCH 085/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index cc29dfa..a103c3b 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -57,8 +57,8 @@ pub enum TypeLayoutSemantics { /// The following types implementing `TypeRepr` exist and can be found in [`shame::any::repr`]: /// /// ``` -/// struct Storage; /// wgsl storage address space layout / OpenGL std430 -/// struct Uniform; /// wgsl uniform address space layout / OpenGL std140 +/// struct Storage; /// wgsl storage address space layout +/// struct Uniform; /// wgsl uniform address space layout /// struct Packed; /// Packed layout /// ``` /// From ff439a957711f14a2ba56bb5f016306d9cb15cae Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:17:12 +0200 Subject: [PATCH 086/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index a103c3b..1ea1c03 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -68,7 +68,10 @@ pub enum TypeLayoutSemantics { /// /// The following method exists for creating new type layouts based on a [`LayoutableType`] /// ``` -/// let layout_type: LayoutableType = f32x1::layoutable_type(); +/// use shame as sm; +/// use sm::any::layout::Layoutable; +/// +/// let layout_type: sm::any::layout::LayoutableType = sm::f32x1::layoutable_type(); /// let repr = Repr::Storage; // or Uniform or Packed /// let _ = TypeLayout::new_layout_for(layout_type, repr); /// ``` From 0577202a71d192ca2dc753f2ec073f46d92dad7e Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 02:17:37 +0200 Subject: [PATCH 087/182] . --- shame/src/frontend/rust_types/type_layout/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 1ea1c03..98a3b30 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -73,7 +73,7 @@ pub enum TypeLayoutSemantics { /// /// let layout_type: sm::any::layout::LayoutableType = sm::f32x1::layoutable_type(); /// let repr = Repr::Storage; // or Uniform or Packed -/// let _ = TypeLayout::new_layout_for(layout_type, repr); +/// let _ = TypeLayout::new_layout_for(&layout_type, repr); /// ``` /// /// The resulting layout will always follow the layout rules of the `Repr`, however, this From 54a2abde2c5d006a84dbc29b230c03291899d431 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:08:03 +0200 Subject: [PATCH 088/182] . --- .../rust_types/type_layout/construction.rs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 31d5cae..a127052 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -190,9 +190,11 @@ fn check_repr_equivalence_for_type( check_sized_fields( ctx, s, - &s.sized_fields, - actual_offsets.sized_field_offsets(), - expected_offsets.sized_field_offsets(), + s.sized_fields.iter().zip( + actual_offsets + .sized_field_offsets() + .zip(expected_offsets.sized_field_offsets()), + ), )?; let (actual_last_offset, _) = actual_offsets.last_field_offset_and_struct_align(); @@ -236,7 +238,13 @@ fn check_compare_sized(ctx: LayoutContext, ty: &SizedType) -> Result<(), LayoutE SizedType::Struct(s) => { let mut actual_offsets = s.field_offsets(ctx.actual_repr); let mut expected_offsets = s.field_offsets(ctx.expected_repr); - check_sized_fields(ctx, s, s.fields(), &mut actual_offsets, &mut expected_offsets) + check_sized_fields( + ctx, + s, + s.fields() + .into_iter() + .zip((&mut actual_offsets).zip(&mut expected_offsets)), + ) } SizedType::Array(a) => { let actual_stride = a.byte_stride(ctx.actual_repr); @@ -259,16 +267,12 @@ fn check_compare_sized(ctx: LayoutContext, ty: &SizedType) -> Result<(), LayoutE } } -fn check_sized_fields( +fn check_sized_fields<'a>( ctx: LayoutContext, s: &(impl Into + Clone), - fields: &[SizedField], - actual_offsets: impl Iterator, - expected_offsets: impl Iterator, + fields_actual_and_expected_offsets: impl Iterator, ) -> Result<(), LayoutError> { - for (i, (field, (actual_offset, expected_offset))) in - fields.iter().zip(actual_offsets.zip(expected_offsets)).enumerate() - { + for (i, (field, (actual_offset, expected_offset))) in fields_actual_and_expected_offsets.enumerate() { if actual_offset != expected_offset { return Err(StructFieldOffsetError { ctx: ctx.into(), From 2afb9115678d106198b80e046a14249beeb90561 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:10:14 +0200 Subject: [PATCH 089/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index a127052..af3802c 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -303,7 +303,7 @@ pub enum LayoutError { requires that the size of uniform buffers on the gpu is known at compile time." )] UniformBufferMustBeSized(&'static str, LayoutableType), - #[error("{0} contains a `PackedVector`, which are not allowed in {1} address space. ")] + #[error("{0} contains a `PackedVector`, which are not allowed in {1} memory layouts ")] MayNotContainPackedVec(LayoutableType, Repr), } From a1ca4a312aa851393e70c5711b5f9e026e2b16cb Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:32:34 +0200 Subject: [PATCH 090/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index af3802c..d5a6f1a 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -355,7 +355,7 @@ impl Display for ArrayStrideError { writeln!( f, "array elements within type `{}` do not satisfy {} layout requirements.", - self.ctx.expected_repr, top_level + top_level, self.ctx.expected_repr, )?; writeln!( f, From 213f70b9b03e322c655d49b51836465d35fae308 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:38:18 +0200 Subject: [PATCH 091/182] . --- .../rust_types/type_layout/construction.rs | 5 ++++- .../rust_types/type_layout/layoutable/mod.rs | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index d5a6f1a..7ef3288 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -414,7 +414,10 @@ impl Display for StructFieldOffsetError { StructKind::Unsized(s) => &s.name, }; - let is_top_level = top_level_name == Some(struct_name); + let is_top_level = match &self.struct_type { + StructKind::Sized(s) => top_level_type == &LayoutableType::Sized(SizedType::Struct(s.clone())), + StructKind::Unsized(s) => top_level_type == &LayoutableType::UnsizedStruct(s.clone()), + }; let structure_def_location = Context::try_with(call_info!(), |ctx| -> Option<_> { match &self.struct_type { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 1570914..0b8bf21 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -24,7 +24,7 @@ pub use builder::{SizedOrArray, FieldOptions}; /// `LayoutableType` does not contain any layout information itself, but a layout /// can be assigned to it using [`GpuTypeLayout`] according to one of the available layout rules: /// storage, uniform or packed. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum LayoutableType { /// A type with a known size. Sized(SizedType), @@ -36,7 +36,7 @@ pub enum LayoutableType { /// Types that have a size which is known at shader creation time. #[allow(missing_docs)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SizedType { Vector(Vector), Matrix(Matrix), @@ -62,20 +62,20 @@ pub struct Matrix { } #[allow(missing_docs)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SizedArray { pub element: Rc, pub len: NonZeroU32, } #[allow(missing_docs)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Atomic { pub scalar: ScalarTypeInteger, } #[allow(missing_docs)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RuntimeSizedArray { pub element: SizedType, } @@ -95,7 +95,7 @@ pub enum ScalarType { } /// A struct with a known fixed size. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SizedStruct { /// The canonical name of the struct. pub name: CanonName, @@ -106,7 +106,7 @@ pub struct SizedStruct { /// A struct whose size is not known before shader runtime. /// /// This struct has a runtime sized array as it's last field. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UnsizedStruct { /// The canonical name of the struct. pub name: CanonName, @@ -117,7 +117,7 @@ pub struct UnsizedStruct { } #[allow(missing_docs)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SizedField { pub name: CanonName, pub custom_min_size: Option, @@ -126,7 +126,7 @@ pub struct SizedField { } #[allow(missing_docs)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RuntimeSizedArrayField { pub name: CanonName, pub custom_min_align: Option, From 1fd09576663cbfb42ed1621cb1e57ecb68e50582 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:45:39 +0200 Subject: [PATCH 092/182] . --- shame/src/frontend/rust_types/layout_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 957ddc9..d1207e6 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -710,7 +710,7 @@ where impl CpuLayout for f32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( layoutable::ScalarType::F32, layoutable::Len::X1, ))) From ffcb83453858f8de46d780fe45412fad1b41d744 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:47:54 +0200 Subject: [PATCH 093/182] . --- shame/src/frontend/rust_types/layout_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index d1207e6..f007afd 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -776,7 +776,7 @@ impl CpuAligned for [T] { impl CpuLayout for [T; N] { fn cpu_layout() -> TypeLayout { - let align = U32PowerOf2::try_from(::alignment() as u32).unwrap(); + let align = ::alignment(); TypeLayout::new( Some(std::mem::size_of::() as u64), From 002d58a38b0d6454486e158e01a00c39f1dd8718 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:48:21 +0200 Subject: [PATCH 094/182] . --- shame/src/frontend/rust_types/layout_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index f007afd..c226ec5 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -819,7 +819,7 @@ impl CpuLayout for [T; N] { impl CpuLayout for [T] { fn cpu_layout() -> TypeLayout { - let align = U32PowerOf2::try_from(::alignment() as u32).unwrap(); + let align = ::alignment(); TypeLayout::new( None, From 92ae2459dfbdf31f5bf0ff756fe7a40d51fbd23d Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:49:38 +0200 Subject: [PATCH 095/182] . --- shame_derive/src/derive_layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index c1e022d..9800024 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -82,7 +82,7 @@ pub fn impl_for_struct( .into_iter(); let none_if_no_cpu_equivalent_type = cpu_attr.is_none().then_some(quote! { None }).into_iter(); - // #[gpu_repr(packed | storage | uniform)] + // #[gpu_repr(packed | storage)] let gpu_repr = util::determine_gpu_repr(&input.attrs)?; if let (Some((span, _)), WhichDerive::CpuLayout) = (&gpu_repr, &which_derive) { bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") From 660036cfff0db36c6c712ac702fa0b83778df645 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:50:06 +0200 Subject: [PATCH 096/182] . --- shame_derive/src/derive_layout.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 9800024..5a3111d 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -87,6 +87,7 @@ pub fn impl_for_struct( if let (Some((span, _)), WhichDerive::CpuLayout) = (&gpu_repr, &which_derive) { bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") } + // if no `#[gpu_repr(_)]` attribute was explicitly specified, we default to `Repr::Storage` let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); let gpu_repr_shame = match gpu_repr { Repr::Packed => quote!( #re::repr::Packed ), From 3db727dbd1a66f1e4d796dd66b941c9eb44423c7 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Mon, 30 Jun 2025 03:55:44 +0200 Subject: [PATCH 097/182] . --- shame_derive/src/derive_layout.rs | 384 +++++++++++++++--------------- 1 file changed, 194 insertions(+), 190 deletions(-) diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 5a3111d..0243896 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -359,228 +359,232 @@ pub fn impl_for_struct( } }; - if matches!(gpu_repr, Repr::Packed) { + match gpu_repr { + Repr::Packed => // this is basically only for vertex buffers, so // we only implement `GpuLayout` and `VertexLayout`, as well as their implied traits - Ok(quote! { - #impl_layoutable - #impl_gpu_layout - #impl_vertex_buffer_layout - #impl_fake_auto_traits - #impl_from_anys - }) - } else { - // non gpu_repr(packed) - let struct_ref_doc = format!( - r#"This struct was generated by `#[derive(shame::GpuLayout)]` + { + Ok(quote! { + #impl_layoutable + #impl_gpu_layout + #impl_vertex_buffer_layout + #impl_fake_auto_traits + #impl_from_anys + }) + } + Repr::Storage => { + // non gpu_repr(packed) + let struct_ref_doc = format!( + r#"This struct was generated by `#[derive(shame::GpuLayout)]` as a version of `{derive_struct_ident}` which holds references to its fields. It is used as the `std::ops::Deref` target of `shame::Ref<{derive_struct_ident}>`"# - ); + ); - Ok(quote! { - #impl_layoutable - #impl_gpu_layout - #impl_vertex_buffer_layout - #impl_fake_auto_traits - #impl_from_anys - - #[doc = #struct_ref_doc] - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - #vis struct #derive_struct_ref_ident<_AS: #re::AddressSpace, _AM: #re::AccessMode> - where #( - #triv #field_type: #re::GpuStore + #re::GpuType, - )* - { - #( - #field_vis #field_ident: #re::Ref<#field_type, _AS, _AM>, + Ok(quote! { + #impl_layoutable + #impl_gpu_layout + #impl_vertex_buffer_layout + #impl_fake_auto_traits + #impl_from_anys + + #[doc = #struct_ref_doc] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + #vis struct #derive_struct_ref_ident<_AS: #re::AddressSpace, _AM: #re::AccessMode> + where #( + #triv #field_type: #re::GpuStore + #re::GpuType, )* - } - - impl<#generics_decl> #re::BufferFields for #derive_struct_ident<#(#idents_of_generics),*> - where - #(#triv #field_type: #re::GpuStore + #re::GpuType,)* - #(#triv #first_fields_type: #re::GpuSized,)* - #triv #last_field_type: #re::GpuAligned, - #where_clause_predicates - { - fn as_anys(&self) -> impl std::borrow::Borrow<[#re::Any]> { - use #re::AsAny; - [ - #(self.#field_ident.as_any()),* - ] + { + #( + #field_vis #field_ident: #re::Ref<#field_type, _AS, _AM>, + )* } - fn clone_fields(&self) -> Self { - Self { - #(#field_ident: std::clone::Clone::clone(&self.#field_ident)),* + impl<#generics_decl> #re::BufferFields for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#triv #field_type: #re::GpuStore + #re::GpuType,)* + #(#triv #first_fields_type: #re::GpuSized,)* + #triv #last_field_type: #re::GpuAligned, + #where_clause_predicates + { + fn as_anys(&self) -> impl std::borrow::Borrow<[#re::Any]> { + use #re::AsAny; + [ + #(self.#field_ident.as_any()),* + ] } - } - - fn get_bufferblock_type() -> #re::ir::BufferBlock { - let mut fields = std::vec::Vec::from([ - #( - #re::ir::SizedField { - name: std::stringify!(#first_fields_ident).into(), - custom_min_size: #first_fields_size, - custom_min_align: #first_fields_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), - ty: <#first_fields_type as #re::GpuSized>::sized_ty(), - } - ),* - ]); - let mut last_unsized = None::<#re::ir::RuntimeSizedArrayField>; - #[allow(clippy::no_effect)] - { - // this part is only here to force a compiler error if the last field - // uses the #[size(n)] attribute but the type is not shame::GpuSized. - #(#enable_if_last_field_has_size_attribute; // only generate the line below if the last field has a #[size(n)] attribute - // compiler error if not shame::GpuSized - fn __() where #last_field_type: #re::GpuSized {} - )* - - match <#last_field_type as #re::GpuAligned>::aligned_ty() { - #re::ir::AlignedType::Sized(ty) => - fields.push(#re::ir::SizedField { - name: std::stringify!(#last_field_ident).into(), - custom_min_size: #last_field_size, - custom_min_align: #last_field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), - ty - }), - #re::ir::AlignedType::RuntimeSizedArray(element_ty) => - last_unsized = Some(#re::ir::RuntimeSizedArrayField { - name: std::stringify!(#last_field_ident).into(), - custom_min_align: #last_field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), - element_ty - }), + fn clone_fields(&self) -> Self { + Self { + #(#field_ident: std::clone::Clone::clone(&self.#field_ident)),* } } - use #re::BufferBlockDefinitionError as E; - match #re::ir::BufferBlock::new( - std::stringify!(#derive_struct_ident).into(), - fields, - last_unsized - ) { - Ok(t) => t, - Err(e) => match e { - E::MustHaveAtLeastOneField => unreachable!(">= 1 field is ensured by derive macro"), - E::FieldNamesMustBeUnique(_) => unreachable!("unique field idents are ensured by rust struct definition"), + fn get_bufferblock_type() -> #re::ir::BufferBlock { + let mut fields = std::vec::Vec::from([ + #( + #re::ir::SizedField { + name: std::stringify!(#first_fields_ident).into(), + custom_min_size: #first_fields_size, + custom_min_align: #first_fields_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), + ty: <#first_fields_type as #re::GpuSized>::sized_ty(), + } + ),* + ]); + + let mut last_unsized = None::<#re::ir::RuntimeSizedArrayField>; + #[allow(clippy::no_effect)] + { + // this part is only here to force a compiler error if the last field + // uses the #[size(n)] attribute but the type is not shame::GpuSized. + #(#enable_if_last_field_has_size_attribute; // only generate the line below if the last field has a #[size(n)] attribute + // compiler error if not shame::GpuSized + fn __() where #last_field_type: #re::GpuSized {} + )* + + match <#last_field_type as #re::GpuAligned>::aligned_ty() { + #re::ir::AlignedType::Sized(ty) => + fields.push(#re::ir::SizedField { + name: std::stringify!(#last_field_ident).into(), + custom_min_size: #last_field_size, + custom_min_align: #last_field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), + ty + }), + #re::ir::AlignedType::RuntimeSizedArray(element_ty) => + last_unsized = Some(#re::ir::RuntimeSizedArrayField { + name: std::stringify!(#last_field_ident).into(), + custom_min_align: #last_field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), + element_ty + }), + } + } + + use #re::BufferBlockDefinitionError as E; + match #re::ir::BufferBlock::new( + std::stringify!(#derive_struct_ident).into(), + fields, + last_unsized + ) { + Ok(t) => t, + Err(e) => match e { + E::MustHaveAtLeastOneField => unreachable!(">= 1 field is ensured by derive macro"), + E::FieldNamesMustBeUnique(_) => unreachable!("unique field idents are ensured by rust struct definition"), + } } } } - } - - impl<#generics_decl> #re::GpuStore for #derive_struct_ident<#(#idents_of_generics),*> - where - #(#triv #field_type: #re::GpuStore + #re::GpuType,)* - #where_clause_predicates - { - type RefFields = #derive_struct_ref_ident; - - fn store_ty() -> #re::ir::StoreType where - #triv Self: #re::GpuType { - unreachable!("Self: !GpuType") - } - fn instantiate_buffer_inner( - args: Result<#re::BindingArgs, #re::InvalidReason>, - bind_ty: #re::BindingType - ) -> #re::BufferInner + impl<#generics_decl> #re::GpuStore for #derive_struct_ident<#(#idents_of_generics),*> where - #triv Self: - #re::NoAtomics + - #re::NoBools + #(#triv #field_type: #re::GpuStore + #re::GpuType,)* + #where_clause_predicates { - #re::BufferInner::new_fields(args, bind_ty) - } + type RefFields = #derive_struct_ref_ident; - fn instantiate_buffer_ref_inner( - args: Result<#re::BindingArgs, #re::InvalidReason>, - bind_ty: #re::BindingType - ) -> #re::BufferRefInner - where - #triv Self: #re::NoBools, - { - #re::BufferRefInner::new_fields(args, bind_ty) - } + fn store_ty() -> #re::ir::StoreType where + #triv Self: #re::GpuType { + unreachable!("Self: !GpuType") + } + + fn instantiate_buffer_inner( + args: Result<#re::BindingArgs, #re::InvalidReason>, + bind_ty: #re::BindingType + ) -> #re::BufferInner + where + #triv Self: + #re::NoAtomics + + #re::NoBools + { + #re::BufferInner::new_fields(args, bind_ty) + } + + fn instantiate_buffer_ref_inner( + args: Result<#re::BindingArgs, #re::InvalidReason>, + bind_ty: #re::BindingType + ) -> #re::BufferRefInner + where + #triv Self: #re::NoBools, + { + #re::BufferRefInner::new_fields(args, bind_ty) + } - fn impl_category() -> #re::GpuStoreImplCategory { - #re::GpuStoreImplCategory::Fields(::get_bufferblock_type()) + fn impl_category() -> #re::GpuStoreImplCategory { + #re::GpuStoreImplCategory::Fields(::get_bufferblock_type()) + } } - } - impl<#generics_decl> #re::SizedFields for #derive_struct_ident<#(#idents_of_generics),*> - where - #(#triv #field_type: #re::GpuSized + #re::GpuStore + #re::GpuType,)* - #where_clause_predicates - { - fn get_sizedstruct_type() -> #re::ir::SizedStruct { - let struct_ = #re::ir::SizedStruct::new_nonempty( - std::stringify!(#derive_struct_ident).into(), - std::vec::Vec::from([ - #( - #re::ir::SizedField { - name: std::stringify!(#first_fields_ident).into(), - custom_min_size: #first_fields_size, - custom_min_align: #first_fields_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), - ty: <#first_fields_type as #re::GpuSized>::sized_ty(), - } - ),* - ]), - #re::ir::SizedField { - name: std::stringify!(#last_field_ident).into(), - custom_min_size: #last_field_size, - custom_min_align: #last_field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), - ty: <#last_field_type as #re::GpuSized>::sized_ty(), + impl<#generics_decl> #re::SizedFields for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#triv #field_type: #re::GpuSized + #re::GpuStore + #re::GpuType,)* + #where_clause_predicates + { + fn get_sizedstruct_type() -> #re::ir::SizedStruct { + let struct_ = #re::ir::SizedStruct::new_nonempty( + std::stringify!(#derive_struct_ident).into(), + std::vec::Vec::from([ + #( + #re::ir::SizedField { + name: std::stringify!(#first_fields_ident).into(), + custom_min_size: #first_fields_size, + custom_min_align: #first_fields_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), + ty: <#first_fields_type as #re::GpuSized>::sized_ty(), + } + ),* + ]), + #re::ir::SizedField { + name: std::stringify!(#last_field_ident).into(), + custom_min_size: #last_field_size, + custom_min_align: #last_field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")), + ty: <#last_field_type as #re::GpuSized>::sized_ty(), + } + ); + match struct_ { + Ok(s) => s, + Err(#re::ir::StructureFieldNamesMustBeUnique { .. }) => unreachable!("field name uniqueness is checked by rust"), } - ); - match struct_ { - Ok(s) => s, - Err(#re::ir::StructureFieldNamesMustBeUnique { .. }) => unreachable!("field name uniqueness is checked by rust"), } } - } - impl<#generics_decl> #re::GetAllFields for #derive_struct_ident<#(#idents_of_generics),*> - where - #(#triv #first_fields_type: #re::GpuSized,)* - #triv #last_field_type: #re::GpuAligned, - #where_clause_predicates - { - fn fields_as_anys_unchecked(self_: #re::Any) -> impl std::borrow::Borrow<[#re::Any]> { - [ - #(self_.get_field(std::stringify!(#field_ident).into())),* - ] + impl<#generics_decl> #re::GetAllFields for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#triv #first_fields_type: #re::GpuSized,)* + #triv #last_field_type: #re::GpuAligned, + #where_clause_predicates + { + fn fields_as_anys_unchecked(self_: #re::Any) -> impl std::borrow::Borrow<[#re::Any]> { + [ + #(self_.get_field(std::stringify!(#field_ident).into())),* + ] + } } - } - impl #re::FromAnys for #derive_struct_ref_ident - where #( - #triv #field_type: #re::GpuStore + #re::GpuType, - )* { - fn expected_num_anys() -> usize {#num_fields} - - #[track_caller] - fn from_anys(mut anys: impl Iterator) -> Self { - use #re::{ - collect_into_array_exact, - push_wrong_amount_of_args_error - }; - const EXPECTED_LEN: usize = #num_fields; - let [#(#field_ident),*] = match collect_into_array_exact::<#re::Any, EXPECTED_LEN>(anys) { - Ok(t) => t, - Err(actual_len) => { - let any = push_wrong_amount_of_args_error(actual_len, EXPECTED_LEN, #re::call_info!()); - [any; EXPECTED_LEN] + impl #re::FromAnys for #derive_struct_ref_ident + where #( + #triv #field_type: #re::GpuStore + #re::GpuType, + )* { + fn expected_num_anys() -> usize {#num_fields} + + #[track_caller] + fn from_anys(mut anys: impl Iterator) -> Self { + use #re::{ + collect_into_array_exact, + push_wrong_amount_of_args_error + }; + const EXPECTED_LEN: usize = #num_fields; + let [#(#field_ident),*] = match collect_into_array_exact::<#re::Any, EXPECTED_LEN>(anys) { + Ok(t) => t, + Err(actual_len) => { + let any = push_wrong_amount_of_args_error(actual_len, EXPECTED_LEN, #re::call_info!()); + [any; EXPECTED_LEN] + } + }; + Self { + #(#field_ident: From::from(#field_ident)),* } - }; - Self { - #(#field_ident: From::from(#field_ident)),* } } - } - }) + }) + } } } WhichDerive::CpuLayout => { From 16f1fda84686b05ee88aeb36385216a21fb2eb05 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 30 Jun 2025 16:05:23 +0200 Subject: [PATCH 098/182] api_showcase glam comment fix --- examples/api_showcase/src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index 9f19cb9..fe48593 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -498,15 +498,19 @@ impl sm::CpuLayout for Mat4 { // using "duck-traiting" allows you to define layouts for foreign cpu-types, // sidestepping the orphan-rule: +// // if you want to try this, add `glam = "0.30.4"` to the Cargo.toml of this +// // example, comment the `Mat4` definition above and uncomment below // use glam::Mat4; // // declare your own trait with a `layout()` function like this -// // This function will be used by the `derive(GpuLayout)` proc macro +// // This function will be used by the `derive(sm::CpuLayout)` proc macro // pub trait MyCpuLayoutTrait { -// fn layout() -> shame::TypeLayout; +// fn cpu_layout() -> shame::TypeLayout; // } // // tell `shame` about the layout semantics of `glam` types +// // here make a promise to `shame` that `glam::Mat4` has identical memory layout +// // to `sm::f32x4x4` // impl MyCpuLayoutTrait for glam::Mat4 { -// fn layout() -> shame::TypeLayout { sm::gpu_layout::() } +// fn cpu_layout() -> shame::TypeLayout { sm::gpu_layout::() } // } From 2e99da1c918d3d0fe71da8a880a9ba86db615272 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 30 Jun 2025 18:01:02 +0200 Subject: [PATCH 099/182] `GpuTypeLayout` comment adjustment --- shame/src/frontend/rust_types/type_layout/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 98a3b30..ab19673 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -112,7 +112,7 @@ pub struct TypeLayout { /// A version of TypeLayout that provides additional compile-time guarantees. /// It is guaranteed to represent a LayoutableType that is layed out in memory using T's layout rules. /// -/// The actual `TypeLayout` can be obtained via `GpuTypeLayout::layout`. +/// An instance of `TypeLayout` (which drops compile time guarantees) can be obtained via `GpuTypeLayout::layout`. /// /// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created /// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by From 78e3d0998fcf9eb21146d2d0765a78ef25f56bd6 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 30 Jun 2025 18:04:34 +0200 Subject: [PATCH 100/182] `LayoutableConversionError` edit --- .../rust_types/type_layout/layoutable/ir_compat.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index bddcd44..bcb8512 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -222,13 +222,12 @@ impl TryFrom for ir::ir_type::RuntimeSizedArrayField { #[error("Type contains bools, which doesn't have a known layout.")] pub struct ContainsBoolsError; -/// Errors that can occur when converting IR types to layoutable types on the gpu. +/// Errors that can occur when converting IR types to layoutable types. +#[allow(missing_docs)] #[derive(thiserror::Error, Debug)] pub enum LayoutableConversionError { - /// Type contains bools, which don't have a standardized memory layout. - #[error("Type contains bools, which don't have a standardized memory layout.")] + #[error("Type contains bools, which don't have a standardized memory layout on the gpu.")] ContainsBool, - /// Type is a handle, which don't have a standardized memory layout. #[error("Type is a handle, which don't have a standardized memory layout.")] IsHandle, } From 0ebfac63f91607829d61e9ded1ad3027b3e01149 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 30 Jun 2025 19:27:08 +0200 Subject: [PATCH 101/182] make `Vector` layout depend on repr --- .../rust_types/type_layout/construction.rs | 2 +- .../type_layout/layoutable/align_size.rs | 1000 +++++++++-------- 2 files changed, 504 insertions(+), 498 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 7ef3288..90c9afe 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -26,7 +26,7 @@ impl TypeLayout { fn from_sized_type(ty: &SizedType, repr: Repr) -> Self { let (size, align, tls) = match &ty { - SizedType::Vector(v) => (v.byte_size(), v.align(repr), TLS::Vector(*v)), + SizedType::Vector(v) => (v.byte_size(repr), v.align(repr), TLS::Vector(*v)), SizedType::Atomic(a) => ( a.byte_size(), a.align(repr), diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 2284481..0f1d58e 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -1,497 +1,503 @@ -use super::super::{Repr}; -use super::*; - -// Size and align of layoutable types // -// https://www.w3.org/TR/WGSL/#address-space-layout-constraints // - -pub(crate) const PACKED_ALIGN: U32PowerOf2 = U32PowerOf2::_1; - -impl LayoutableType { - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn byte_size(&self, repr: Repr) -> Option { - match self { - LayoutableType::Sized(s) => Some(s.byte_size(repr)), - LayoutableType::UnsizedStruct(_) | LayoutableType::RuntimeSizedArray(_) => None, - } - } - - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - match self { - LayoutableType::Sized(s) => s.align(repr), - LayoutableType::UnsizedStruct(s) => s.align(repr), - LayoutableType::RuntimeSizedArray(a) => a.align(repr), - } - } - - /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. - pub fn byte_size_and_align(&self, repr: Repr) -> (Option, U32PowerOf2) { - match self { - LayoutableType::Sized(s) => { - let (size, align) = s.byte_size_and_align(repr); - (Some(size), align) - } - LayoutableType::UnsizedStruct(s) => (None, s.align(repr)), - LayoutableType::RuntimeSizedArray(a) => (None, a.align(repr)), - } - } -} - -impl SizedType { - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn byte_size(&self, repr: Repr) -> u64 { - match self { - SizedType::Array(a) => a.byte_size(repr), - SizedType::Vector(v) => v.byte_size(), - SizedType::Matrix(m) => m.byte_size(repr), - SizedType::Atomic(a) => a.byte_size(), - SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, - SizedType::Struct(s) => s.byte_size_and_align(repr).0, - } - } - - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - match self { - SizedType::Array(a) => a.align(repr), - SizedType::Vector(v) => v.align(repr), - SizedType::Matrix(m) => m.align(repr), - SizedType::Atomic(a) => a.align(repr), - SizedType::PackedVec(v) => v.align(repr), - SizedType::Struct(s) => s.byte_size_and_align(repr).1, - } - } - - /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. - pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { - match self { - SizedType::Struct(s) => s.byte_size_and_align(repr), - non_struct => (non_struct.byte_size(repr), non_struct.align(repr)), - } - } -} - - -impl SizedStruct { - /// Returns [`FieldOffsetsSized`], which serves as an iterator over the offsets of the - /// fields of this struct. `FieldOffsetsSized::struct_byte_size_and_align` can be - /// used to efficiently obtain the byte_size and align. - pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsSized { - FieldOffsetsSized(FieldOffsets::new(self.fields(), repr)) - } - - /// Returns (byte_size, align) - /// - /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. - pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { - self.field_offsets(repr).struct_byte_size_and_align() - } -} - -/// An iterator over the offsets of sized fields. -pub struct FieldOffsets<'a> { - fields: &'a [SizedField], - field_index: usize, - calc: LayoutCalculator, - repr: Repr, -} -impl Iterator for FieldOffsets<'_> { - type Item = u64; - - fn next(&mut self) -> Option { - self.field_index += 1; - self.fields.get(self.field_index - 1).map(|field| { - let (size, align) = field.ty.byte_size_and_align(self.repr); - let is_struct = matches!(field.ty, SizedType::Struct(_)); - - self.calc - .extend(size, align, field.custom_min_size, field.custom_min_align, is_struct) - }) - } -} -impl<'a> FieldOffsets<'a> { - fn new(fields: &'a [SizedField], repr: Repr) -> Self { - Self { - fields, - field_index: 0, - calc: LayoutCalculator::new(repr), - repr, - } - } -} - -/// Iterator over the field offsets of a `SizedStruct`. -// The difference to `FieldOffsets` is that it also offers a `struct_byte_size_and_align` method. -pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); -impl Iterator for FieldOffsetsSized<'_> { - type Item = u64; - fn next(&mut self) -> Option { self.0.next() } -} -impl<'a> FieldOffsetsSized<'a> { - /// Consumes self and calculates the byte size and align of a struct - /// with exactly the sized fields that this FieldOffsets was created with. - pub fn struct_byte_size_and_align(mut self) -> (u64, U32PowerOf2) { - // Finishing layout calculations - // using count only to advance iterator to the end - (&mut self.0).count(); - ( - self.0.calc.byte_size(), - adjust_struct_alignment_for_repr(self.0.calc.align(), self.0.repr), - ) - } - - /// Returns the inner iterator over sized fields. - pub fn into_inner(self) -> FieldOffsets<'a> { self.0 } -} - -/// The field offsets of an `UnsizedStruct`. -/// -/// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. -/// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset -/// and the struct's align -pub struct FieldOffsetsUnsized<'a> { - sized: FieldOffsets<'a>, - last_unsized: &'a RuntimeSizedArrayField, -} - -impl<'a> FieldOffsetsUnsized<'a> { - fn new(sized_fields: &'a [SizedField], last_unsized: &'a RuntimeSizedArrayField, repr: Repr) -> Self { - Self { - sized: FieldOffsets::new(sized_fields, repr), - last_unsized, - } - } - - /// Returns an iterator over the sized field offsets. - pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { &mut self.sized } - - /// Returns the last field's offset and the struct's align. - pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { - // Finishing layout calculations - // using count only to advance iterator to the end - (&mut self.sized).count(); - let array_align = self.last_unsized.array.align(self.sized.repr); - let custom_min_align = self.last_unsized.custom_min_align; - let (offset, align) = self.sized.calc.extend_unsized(array_align, custom_min_align); - (offset, adjust_struct_alignment_for_repr(align, self.sized.repr)) - } - - /// Returns the inner iterator over sized fields. - pub fn into_inner(self) -> FieldOffsets<'a> { self.sized } -} - -impl UnsizedStruct { - /// Returns [`FieldOffsetsUnsized`]. - /// - /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. - /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset - /// and the struct's align - pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsUnsized { - FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, repr) - } - - /// This is expensive as it calculates the byte align by traversing all fields recursively. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } -} - -const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { - match repr { - // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage => align, - Repr::Uniform => round_up_align(U32PowerOf2::_16, align), - Repr::Packed => PACKED_ALIGN, - } -} - -#[allow(missing_docs)] -impl Vector { - pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } - - pub const fn byte_size(&self) -> u64 { self.len.as_u64() * self.scalar.byte_size() } - - pub const fn align(&self, repr: Repr) -> U32PowerOf2 { - match repr { - Repr::Packed => return PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} - } - - let len = match self.len { - Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), - Len::X3 => 4, - }; - U32PowerOf2::try_from_u32(len * self.scalar.align().as_u32()) - .expect("power of 2 * power of 2 = power of 2. Highest operands are around 4 * 16 so overflow is unlikely") - } -} - -#[allow(missing_docs)] -impl ScalarType { - pub const fn byte_size(&self) -> u64 { - match self { - ScalarType::F16 => 2, - ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => 4, - ScalarType::F64 => 8, - } - } - - pub const fn align(&self) -> U32PowerOf2 { - match self { - ScalarType::F16 => U32PowerOf2::_2, - ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, - ScalarType::F64 => U32PowerOf2::_8, - } - } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy)] -pub enum MatrixMajor { - Row, - Column, -} - -#[allow(missing_docs)] -impl Matrix { - pub const fn byte_size(&self, repr: Repr) -> u64 { - let (vec, array_len) = self.as_vector_array(); - let array_stride = array_stride(vec.align(repr), vec.byte_size()); - array_size(array_stride, array_len) - } - - pub const fn align(&self, repr: Repr) -> U32PowerOf2 { - let (vec, _) = self.as_vector_array(); - // AlignOf(vecR) - vec.align(repr) - } - - const fn as_vector_array(&self) -> (Vector, NonZeroU32) { - let major = MatrixMajor::Column; // This can be made a parameter in the future. - let (vec_len, array_len): (Len, NonZeroU32) = match major { - MatrixMajor::Column => (self.rows.as_len(), self.columns.as_non_zero_u32()), - MatrixMajor::Row => (self.columns.as_len(), self.rows.as_non_zero_u32()), - }; - ( - Vector { - len: vec_len, - scalar: self.scalar.as_scalar_type(), - }, - array_len, - ) - } -} - -#[allow(missing_docs)] -impl Atomic { - pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } - pub const fn align(&self, repr: Repr) -> U32PowerOf2 { - if repr.is_packed() { - return PACKED_ALIGN; - } - self.scalar.as_scalar_type().align() - } -} - -#[allow(missing_docs)] -impl SizedArray { - pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } - - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } - - pub fn byte_stride(&self, repr: Repr) -> u64 { - let (element_size, element_align) = self.element.byte_size_and_align(repr); - array_stride(element_align, element_size) - } -} - -/// Returns an array's size given it's stride and length. -/// -/// Note, this is independent of layout rules (`Repr`). -pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } - -/// Returns an array's size given the alignment of it's elements. -pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { - match repr { - // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage => element_align, - Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), - Repr::Packed => PACKED_ALIGN, - } -} - -/// Returns an array's size=>stride (the distance between consecutive elements) given the alignment and size of its elements. -pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { - // Arrays of element type T must have an element stride that is a multiple of the - // RequiredAlignOf(T, C) for the address space C: - round_up(element_align.as_u64(), element_size) -} - -#[allow(missing_docs)] -impl RuntimeSizedArray { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } - - pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr)) } -} - -#[allow(missing_docs)] -impl SizedField { - pub fn byte_size(&self, repr: Repr) -> u64 { - LayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) - } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. - // This is intended! - LayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) - } -} - -#[allow(missing_docs)] -impl RuntimeSizedArrayField { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. - // This is intended! - LayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) - } -} - -pub const fn round_up(multiple_of: u64, n: u64) -> u64 { - match multiple_of { - 0 => match n { - 0 => 0, - _ => panic!("cannot round up n to a multiple of 0"), - }, - k @ 1.. => n.div_ceil(k) * k, - } -} - -pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32PowerOf2 { - let rounded_up = round_up(multiple_of.as_u64(), n.as_u64()); - // n <= multiple_of -> rounded_up = multiple_of - // n > multiple_of -> rounded_up = n, since both are powers of 2, n must already - // be a multiple of multiple_of - // In both cases rounded_up is a power of 2 - U32PowerOf2::try_from_u32(rounded_up as u32).unwrap() -} - -/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. -/// -/// If `LayoutCalculator` is created with `repr == Repr::Packed`, provided `field_align`s -/// are ignored and the field is inserted directly after the previous field. However, -/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. -#[derive(Debug, Clone)] -pub struct LayoutCalculator { - next_offset_min: u64, - align: U32PowerOf2, - repr: Repr, -} - -impl LayoutCalculator { - /// Creates a new `LayoutCalculator`, which calculates the size, align and - /// the field offsets of a gpu struct. - pub const fn new(repr: Repr) -> Self { - Self { - next_offset_min: 0, - align: U32PowerOf2::_1, - repr, - } - } - - /// Extends the layout by a field. - /// - /// `is_struct` must be true if the field is a struct. - /// - /// Returns the field's offset. - pub const fn extend( - &mut self, - field_size: u64, - mut field_align: U32PowerOf2, - custom_min_size: Option, - custom_min_align: Option, - is_struct: bool, - ) -> u64 { - // Just in case the user didn't already do this. - match self.repr { - Repr::Packed => field_align = PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} - } - - let size = Self::calculate_byte_size(field_size, custom_min_size); - let align = Self::calculate_align(field_align, custom_min_align); - - let offset = self.next_field_offset(align, custom_min_align); - self.next_offset_min = match (self.repr, is_struct) { - // The uniform address space requires that: - // - If a structure member itself has a structure type S, then the number of - // bytes between the start of that member and the start of any following - // member must be at least roundUp(16, SizeOf(S)). - (Repr::Uniform, true) => round_up(16, offset + size), - (Repr::Storage | Repr::Packed, _) | (Repr::Uniform, false) => offset + size, - }; - self.align = self.align.max(align); - - offset - } - - /// Extends the layout by a runtime sized array field given it's align. - /// - /// Returns (last field offset, align) - /// - /// `self` is consumed, so that no further fields may be extended, because - /// only the last field may be unsized. - pub const fn extend_unsized( - mut self, - mut field_align: U32PowerOf2, - custom_min_align: Option, - ) -> (u64, U32PowerOf2) { - // Just in case the user didn't already do this. - match self.repr { - Repr::Packed => field_align = PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} - } - - let align = Self::calculate_align(field_align, custom_min_align); - - let offset = self.next_field_offset(align, custom_min_align); - self.align = self.align.max(align); - - (offset, self.align) - } - - /// Returns the byte size of the struct. - // wgsl spec: - // roundUp(AlignOf(S), justPastLastMember) - // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) - // - // self.next_offset_min is justPastLastMember already. - pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } - - /// Returns the align of the struct. - pub const fn align(&self) -> U32PowerOf2 { self.align } - - const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { - let field_align = Self::calculate_align(field_align, field_custom_min_align); - match (self.repr, field_custom_min_align) { - (Repr::Packed, None) => self.next_offset_min, - (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), - (Repr::Storage | Repr::Uniform, _) => round_up(field_align.as_u64(), self.next_offset_min), - } - } - - pub(crate) const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { - // const byte_size.max(custom_min_size.unwrap_or(0)) - if let Some(min_size) = custom_min_size { - if min_size > byte_size { - return min_size; - } - } - byte_size - } - - pub(crate) const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) - if let Some(min_align) = custom_min_align { - align.max(min_align) - } else { - align - } - } -} +use super::super::{Repr}; +use super::*; + +// Size and align of layoutable types // +// https://www.w3.org/TR/WGSL/#address-space-layout-constraints // + +pub(crate) const PACKED_ALIGN: U32PowerOf2 = U32PowerOf2::_1; + +impl LayoutableType { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn byte_size(&self, repr: Repr) -> Option { + match self { + LayoutableType::Sized(s) => Some(s.byte_size(repr)), + LayoutableType::UnsizedStruct(_) | LayoutableType::RuntimeSizedArray(_) => None, + } + } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + match self { + LayoutableType::Sized(s) => s.align(repr), + LayoutableType::UnsizedStruct(s) => s.align(repr), + LayoutableType::RuntimeSizedArray(a) => a.align(repr), + } + } + + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. + pub fn byte_size_and_align(&self, repr: Repr) -> (Option, U32PowerOf2) { + match self { + LayoutableType::Sized(s) => { + let (size, align) = s.byte_size_and_align(repr); + (Some(size), align) + } + LayoutableType::UnsizedStruct(s) => (None, s.align(repr)), + LayoutableType::RuntimeSizedArray(a) => (None, a.align(repr)), + } + } +} + +impl SizedType { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn byte_size(&self, repr: Repr) -> u64 { + match self { + SizedType::Array(a) => a.byte_size(repr), + SizedType::Vector(v) => v.byte_size(repr), + SizedType::Matrix(m) => m.byte_size(repr), + SizedType::Atomic(a) => a.byte_size(), + SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, + SizedType::Struct(s) => s.byte_size_and_align(repr).0, + } + } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + match self { + SizedType::Array(a) => a.align(repr), + SizedType::Vector(v) => v.align(repr), + SizedType::Matrix(m) => m.align(repr), + SizedType::Atomic(a) => a.align(repr), + SizedType::PackedVec(v) => v.align(repr), + SizedType::Struct(s) => s.byte_size_and_align(repr).1, + } + } + + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. + pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + match self { + SizedType::Struct(s) => s.byte_size_and_align(repr), + non_struct => (non_struct.byte_size(repr), non_struct.align(repr)), + } + } +} + + +impl SizedStruct { + /// Returns [`FieldOffsetsSized`], which serves as an iterator over the offsets of the + /// fields of this struct. `FieldOffsetsSized::struct_byte_size_and_align` can be + /// used to efficiently obtain the byte_size and align. + pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsSized { + FieldOffsetsSized(FieldOffsets::new(self.fields(), repr)) + } + + /// Returns (byte_size, align) + /// + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. + pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + self.field_offsets(repr).struct_byte_size_and_align() + } +} + +/// An iterator over the offsets of sized fields. +pub struct FieldOffsets<'a> { + fields: &'a [SizedField], + field_index: usize, + calc: LayoutCalculator, + repr: Repr, +} +impl Iterator for FieldOffsets<'_> { + type Item = u64; + + fn next(&mut self) -> Option { + self.field_index += 1; + self.fields.get(self.field_index - 1).map(|field| { + let (size, align) = field.ty.byte_size_and_align(self.repr); + let is_struct = matches!(field.ty, SizedType::Struct(_)); + + self.calc + .extend(size, align, field.custom_min_size, field.custom_min_align, is_struct) + }) + } +} +impl<'a> FieldOffsets<'a> { + fn new(fields: &'a [SizedField], repr: Repr) -> Self { + Self { + fields, + field_index: 0, + calc: LayoutCalculator::new(repr), + repr, + } + } +} + +/// Iterator over the field offsets of a `SizedStruct`. +// The difference to `FieldOffsets` is that it also offers a `struct_byte_size_and_align` method. +pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); +impl Iterator for FieldOffsetsSized<'_> { + type Item = u64; + fn next(&mut self) -> Option { self.0.next() } +} +impl<'a> FieldOffsetsSized<'a> { + /// Consumes self and calculates the byte size and align of a struct + /// with exactly the sized fields that this FieldOffsets was created with. + pub fn struct_byte_size_and_align(mut self) -> (u64, U32PowerOf2) { + // Finishing layout calculations + // using count only to advance iterator to the end + (&mut self.0).count(); + ( + self.0.calc.byte_size(), + adjust_struct_alignment_for_repr(self.0.calc.align(), self.0.repr), + ) + } + + /// Returns the inner iterator over sized fields. + pub fn into_inner(self) -> FieldOffsets<'a> { self.0 } +} + +/// The field offsets of an `UnsizedStruct`. +/// +/// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. +/// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset +/// and the struct's align +pub struct FieldOffsetsUnsized<'a> { + sized: FieldOffsets<'a>, + last_unsized: &'a RuntimeSizedArrayField, +} + +impl<'a> FieldOffsetsUnsized<'a> { + fn new(sized_fields: &'a [SizedField], last_unsized: &'a RuntimeSizedArrayField, repr: Repr) -> Self { + Self { + sized: FieldOffsets::new(sized_fields, repr), + last_unsized, + } + } + + /// Returns an iterator over the sized field offsets. + pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { &mut self.sized } + + /// Returns the last field's offset and the struct's align. + pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { + // Finishing layout calculations + // using count only to advance iterator to the end + (&mut self.sized).count(); + let array_align = self.last_unsized.array.align(self.sized.repr); + let custom_min_align = self.last_unsized.custom_min_align; + let (offset, align) = self.sized.calc.extend_unsized(array_align, custom_min_align); + (offset, adjust_struct_alignment_for_repr(align, self.sized.repr)) + } + + /// Returns the inner iterator over sized fields. + pub fn into_inner(self) -> FieldOffsets<'a> { self.sized } +} + +impl UnsizedStruct { + /// Returns [`FieldOffsetsUnsized`]. + /// + /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. + /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset + /// and the struct's align + pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsUnsized { + FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, repr) + } + + /// This is expensive as it calculates the byte align by traversing all fields recursively. + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } +} + +const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage => align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + Repr::Packed => PACKED_ALIGN, + } +} + +#[allow(missing_docs)] +impl Vector { + pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } + + pub const fn byte_size(&self, repr: Repr) -> u64 { + match repr { + Repr::Storage | Repr::Uniform | Repr::Packed => self.len.as_u64() * self.scalar.byte_size(), + } + } + + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + match repr { + Repr::Packed => PACKED_ALIGN, + Repr::Storage | Repr::Uniform => { + let po2_len = match self.len { + Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), + Len::X3 => 4, + }; + let po2_align = self.scalar.align(); + U32PowerOf2::try_from_u32(po2_len * po2_align.as_u32()).expect( + "power of 2 * power of 2 = power of 2. Highest operands are around 4 * 16 so overflow is unlikely", + ) + } + } + } +} + +#[allow(missing_docs)] +impl ScalarType { + pub const fn byte_size(&self) -> u64 { + match self { + ScalarType::F16 => 2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => 4, + ScalarType::F64 => 8, + } + } + + pub const fn align(&self) -> U32PowerOf2 { + match self { + ScalarType::F16 => U32PowerOf2::_2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, + ScalarType::F64 => U32PowerOf2::_8, + } + } +} + +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy)] +pub enum MatrixMajor { + Row, + Column, +} + +#[allow(missing_docs)] +impl Matrix { + pub const fn byte_size(&self, repr: Repr) -> u64 { + let (vec, array_len) = self.as_vector_array(); + let array_stride = array_stride(vec.align(repr), vec.byte_size(repr)); + array_size(array_stride, array_len) + } + + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + let (vec, _) = self.as_vector_array(); + // AlignOf(vecR) + vec.align(repr) + } + + const fn as_vector_array(&self) -> (Vector, NonZeroU32) { + let major = MatrixMajor::Column; // This can be made a parameter in the future. + let (vec_len, array_len): (Len, NonZeroU32) = match major { + MatrixMajor::Column => (self.rows.as_len(), self.columns.as_non_zero_u32()), + MatrixMajor::Row => (self.columns.as_len(), self.rows.as_non_zero_u32()), + }; + ( + Vector { + len: vec_len, + scalar: self.scalar.as_scalar_type(), + }, + array_len, + ) + } +} + +#[allow(missing_docs)] +impl Atomic { + pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + if repr.is_packed() { + return PACKED_ALIGN; + } + self.scalar.as_scalar_type().align() + } +} + +#[allow(missing_docs)] +impl SizedArray { + pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } + + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } + + pub fn byte_stride(&self, repr: Repr) -> u64 { + let (element_size, element_align) = self.element.byte_size_and_align(repr); + array_stride(element_align, element_size) + } +} + +/// Returns an array's size given it's stride and length. +/// +/// Note, this is independent of layout rules (`Repr`). +pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } + +/// Returns an array's size given the alignment of it's elements. +pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage => element_align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), + Repr::Packed => PACKED_ALIGN, + } +} + +/// Returns an array's size=>stride (the distance between consecutive elements) given the alignment and size of its elements. +pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { + // Arrays of element type T must have an element stride that is a multiple of the + // RequiredAlignOf(T, C) for the address space C: + round_up(element_align.as_u64(), element_size) +} + +#[allow(missing_docs)] +impl RuntimeSizedArray { + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } + + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr)) } +} + +#[allow(missing_docs)] +impl SizedField { + pub fn byte_size(&self, repr: Repr) -> u64 { + LayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) + } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. + // This is intended! + LayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) + } +} + +#[allow(missing_docs)] +impl RuntimeSizedArrayField { + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. + // This is intended! + LayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) + } +} + +pub const fn round_up(multiple_of: u64, n: u64) -> u64 { + match multiple_of { + 0 => match n { + 0 => 0, + _ => panic!("cannot round up n to a multiple of 0"), + }, + k @ 1.. => n.div_ceil(k) * k, + } +} + +pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32PowerOf2 { + let rounded_up = round_up(multiple_of.as_u64(), n.as_u64()); + // n <= multiple_of -> rounded_up = multiple_of + // n > multiple_of -> rounded_up = n, since both are powers of 2, n must already + // be a multiple of multiple_of + // In both cases rounded_up is a power of 2 + U32PowerOf2::try_from_u32(rounded_up as u32).unwrap() +} + +/// `LayoutCalculator` helps calculate the size, align and the field offsets of a struct. +/// +/// If `LayoutCalculator` is created with `repr == Repr::Packed`, provided `field_align`s +/// are ignored and the field is inserted directly after the previous field. However, +/// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. +#[derive(Debug, Clone)] +pub struct LayoutCalculator { + next_offset_min: u64, + align: U32PowerOf2, + repr: Repr, +} + +impl LayoutCalculator { + /// Creates a new `LayoutCalculator`, which calculates the size, align and + /// the field offsets of a gpu struct. + pub const fn new(repr: Repr) -> Self { + Self { + next_offset_min: 0, + align: U32PowerOf2::_1, + repr, + } + } + + /// Extends the layout by a field. + /// + /// `is_struct` must be true if the field is a struct. + /// + /// Returns the field's offset. + pub const fn extend( + &mut self, + field_size: u64, + mut field_align: U32PowerOf2, + custom_min_size: Option, + custom_min_align: Option, + is_struct: bool, + ) -> u64 { + // Just in case the user didn't already do this. + match self.repr { + Repr::Packed => field_align = PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} + } + + let size = Self::calculate_byte_size(field_size, custom_min_size); + let align = Self::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.next_offset_min = match (self.repr, is_struct) { + // The uniform address space requires that: + // - If a structure member itself has a structure type S, then the number of + // bytes between the start of that member and the start of any following + // member must be at least roundUp(16, SizeOf(S)). + (Repr::Uniform, true) => round_up(16, offset + size), + (Repr::Storage | Repr::Packed, _) | (Repr::Uniform, false) => offset + size, + }; + self.align = self.align.max(align); + + offset + } + + /// Extends the layout by a runtime sized array field given it's align. + /// + /// Returns (last field offset, align) + /// + /// `self` is consumed, so that no further fields may be extended, because + /// only the last field may be unsized. + pub const fn extend_unsized( + mut self, + mut field_align: U32PowerOf2, + custom_min_align: Option, + ) -> (u64, U32PowerOf2) { + // Just in case the user didn't already do this. + match self.repr { + Repr::Packed => field_align = PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} + } + + let align = Self::calculate_align(field_align, custom_min_align); + + let offset = self.next_field_offset(align, custom_min_align); + self.align = self.align.max(align); + + (offset, self.align) + } + + /// Returns the byte size of the struct. + // wgsl spec: + // roundUp(AlignOf(S), justPastLastMember) + // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) + // + // self.next_offset_min is justPastLastMember already. + pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } + + /// Returns the align of the struct. + pub const fn align(&self) -> U32PowerOf2 { self.align } + + const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { + let field_align = Self::calculate_align(field_align, field_custom_min_align); + match (self.repr, field_custom_min_align) { + (Repr::Packed, None) => self.next_offset_min, + (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + (Repr::Storage | Repr::Uniform, _) => round_up(field_align.as_u64(), self.next_offset_min), + } + } + + pub(crate) const fn calculate_byte_size(byte_size: u64, custom_min_size: Option) -> u64 { + // const byte_size.max(custom_min_size.unwrap_or(0)) + if let Some(min_size) = custom_min_size { + if min_size > byte_size { + return min_size; + } + } + byte_size + } + + pub(crate) const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align + } + } +} From 766562ab73751409251a8fc33512a2b4f635e987 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Thu, 3 Jul 2025 09:24:31 +0200 Subject: [PATCH 102/182] . --- shame/src/frontend/rust_types/layout_traits.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index c226ec5..b18e0c9 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -722,7 +722,7 @@ impl CpuLayout for f32 { impl CpuLayout for f64 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( layoutable::ScalarType::F64, layoutable::Len::X1, ))) @@ -731,7 +731,7 @@ impl CpuLayout for f64 { impl CpuLayout for u32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( layoutable::ScalarType::U32, layoutable::Len::X1, ))) @@ -740,7 +740,7 @@ impl CpuLayout for u32 { impl CpuLayout for i32 { fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( + TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( layoutable::ScalarType::I32, layoutable::Len::X1, ))) From 9dda47c7394a4cf02af35e6dce45104b19be80dd Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 09:55:37 +0200 Subject: [PATCH 103/182] . --- shame_derive/src/derive_layout.rs | 2 +- shame_derive/src/util.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 0243896..9350576 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -83,7 +83,7 @@ pub fn impl_for_struct( let none_if_no_cpu_equivalent_type = cpu_attr.is_none().then_some(quote! { None }).into_iter(); // #[gpu_repr(packed | storage)] - let gpu_repr = util::determine_gpu_repr(&input.attrs)?; + let gpu_repr = util::try_find_gpu_repr(&input.attrs)?; if let (Some((span, _)), WhichDerive::CpuLayout) = (&gpu_repr, &which_derive) { bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") } diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index 4e23ebc..082a492 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -32,7 +32,7 @@ pub enum Repr { Storage, } -pub fn determine_gpu_repr(attribs: &[syn::Attribute]) -> Result> { +pub fn try_find_gpu_repr(attribs: &[syn::Attribute]) -> Result> { let mut repr = Repr::Storage; for a in attribs { if a.path().is_ident("gpu_repr") { From 74b910596037ddf23e2cf3862b38969c88c25058 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:16:53 +0200 Subject: [PATCH 104/182] merge LayoutableType with Layoutable --- examples/type_layout/src/main.rs | 2 +- shame/src/common/proc_macro_reexports.rs | 1 - shame/src/frontend/encoding/buffer.rs | 4 +- shame/src/frontend/rust_types/array.rs | 11 +--- shame/src/frontend/rust_types/atomic.rs | 9 +-- .../src/frontend/rust_types/layout_traits.rs | 5 +- shame/src/frontend/rust_types/mat.rs | 9 +-- shame/src/frontend/rust_types/packed_vec.rs | 9 +-- shame/src/frontend/rust_types/struct_.rs | 5 +- .../rust_types/type_layout/layoutable/mod.rs | 20 ++++-- shame/src/frontend/rust_types/vec.rs | 12 +--- shame/src/lib.rs | 1 - shame_derive/src/derive_layout.rs | 65 +++++++------------ 13 files changed, 59 insertions(+), 94 deletions(-) diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 3c71232..6dc3256 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -6,7 +6,7 @@ use shame::{ any::{ self, layout::{ - self, FieldOptions, LayoutableSized, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, + self, FieldOptions, Layoutable, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, UnsizedStruct, Vector, GpuTypeLayout, }, U32PowerOf2, diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 2b2e71e..4bbb888 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -43,7 +43,6 @@ pub use crate::frontend::rust_types::type_traits::VertexAttribute; pub use crate::frontend::rust_types::type_traits::GpuLayoutField; pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; -pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableSized; pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index c6f3c1c..d0cf58e 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -1,4 +1,4 @@ -use crate::any::layout::LayoutableSized; +use crate::any::layout::Layoutable; use crate::common::proc_macro_reexports::GpuStoreImplCategory; use crate::frontend::any::shared_io::{BindPath, BindingType, BufferBindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -319,7 +319,7 @@ fn store_type_from_impl_category(category: GpuStoreImplCategory) -> ir::StoreTyp #[rustfmt::skip] impl Binding for Buffer, AS, DYN_OFFSET> where - T: GpuType + GpuSized + GpuLayout + LayoutableSized + T: GpuType + GpuSized + GpuLayout + Layoutable { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index faebced..9ae117e 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -12,7 +12,7 @@ use super::type_traits::{ use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; -use crate::any::layout::{Layoutable, LayoutableSized}; +use crate::any::layout::{Layoutable}; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; @@ -156,12 +156,7 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} -impl LayoutableSized for Array> { - fn layoutable_type_sized() -> layoutable::SizedType { - layoutable::SizedArray::new(Rc::new(T::layoutable_type_sized()), Size::::nonzero()).into() - } -} -impl Layoutable for Array { +impl Layoutable for Array { fn layoutable_type() -> layoutable::LayoutableType { match N::LEN { Some(n) => layoutable::SizedArray::new(Rc::new(T::layoutable_type_sized()), n).into(), @@ -178,7 +173,7 @@ impl ToGpuType for Array { fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { Some(self) } } -impl GpuLayout for Array { +impl GpuLayout for Array { type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index 0e461c7..aec5ca4 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -8,7 +8,7 @@ use super::{ scalar_type::{ScalarType, ScalarTypeInteger}, type_layout::{ self, - layoutable::{self, LayoutableSized}, + layoutable::{self}, repr, TypeLayout, }, type_traits::{ @@ -133,17 +133,14 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } -impl LayoutableSized for Atomic { - fn layoutable_type_sized() -> layoutable::SizedType { +impl Layoutable for Atomic { + fn layoutable_type() -> layoutable::LayoutableType { layoutable::Atomic { scalar: T::SCALAR_TYPE_INTEGER, } .into() } } -impl Layoutable for Atomic { - fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } -} impl GpuLayout for Atomic { type GpuRepr = repr::Storage; diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index b18e0c9..5f52d72 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,4 +1,4 @@ -use crate::any::layout::{Layoutable, LayoutableSized, Repr}; +use crate::any::layout::{Layoutable, Repr}; use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; @@ -554,9 +554,6 @@ where } } -impl LayoutableSized for GpuT { - fn layoutable_type_sized() -> layoutable::SizedType { todo!() } -} impl Layoutable for GpuT { fn layoutable_type() -> layoutable::LayoutableType { todo!() } } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 07d3404..0757b77 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -9,7 +9,7 @@ use super::{ scalar_type::{ScalarType, ScalarTypeFp}, type_layout::{ self, - layoutable::{self, LayoutableSized}, + layoutable::{self}, repr, TypeLayout, }, type_traits::{ @@ -54,8 +54,8 @@ impl Default for mat { } } -impl LayoutableSized for mat { - fn layoutable_type_sized() -> layoutable::SizedType { +impl Layoutable for mat { + fn layoutable_type() -> layoutable::LayoutableType { layoutable::Matrix { columns: C::LEN2, rows: R::LEN2, @@ -64,9 +64,6 @@ impl LayoutableSized for mat { .into() } } -impl Layoutable for mat { - fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } -} impl GpuLayout for mat { type GpuRepr = repr::Storage; diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index a810c05..d769701 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ any::{ - layout::{Layoutable, LayoutableSized}, + layout::{Layoutable}, AsAny, DataPackingFn, }, common::floating_point::f16, @@ -133,8 +133,8 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} -impl LayoutableSized for PackedVec { - fn layoutable_type_sized() -> layoutable::SizedType { +impl Layoutable for PackedVec { + fn layoutable_type() -> layoutable::LayoutableType { layoutable::PackedVector { scalar_type: T::SCALAR_TYPE, bits_per_component: T::BITS_PER_COMPONENT, @@ -143,9 +143,6 @@ impl LayoutableSized for PackedVec { .into() } } -impl Layoutable for PackedVec { - fn layoutable_type() -> layoutable::LayoutableType { Self::layoutable_type_sized().into() } -} impl GpuLayout for PackedVec { type GpuRepr = repr::Storage; diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index f50c185..d8fa3f4 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,4 +1,4 @@ -use crate::any::layout::{Layoutable, LayoutableSized}; +use crate::any::layout::{Layoutable}; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -135,9 +135,6 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl LayoutableSized for Struct { - fn layoutable_type_sized() -> layoutable::SizedType { T::layoutable_type_sized() } -} impl Layoutable for Struct { fn layoutable_type() -> layoutable::LayoutableType { T::layoutable_type() } } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 0b8bf21..a913b13 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -7,6 +7,7 @@ use crate::{ call_info, common::prettify::set_color, ir::{self, ir_type::BufferBlockDefinitionError, recording::Context, StructureFieldNamesMustBeUnique}, + GpuSized, }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; @@ -137,11 +138,20 @@ pub struct RuntimeSizedArrayField { pub trait Layoutable { /// Returns the `LayoutableType` representation for this type. fn layoutable_type() -> LayoutableType; -} -/// Trait for types that have a well-defined memory layout and statically known size. -pub trait LayoutableSized: Layoutable { - /// Returns the `SizedType` representation for this type. - fn layoutable_type_sized() -> SizedType; + + /// When the type is `GpuSized`, this method can be used to immediately get + /// the `LayoutableType::Sized` variant's inner `SizedType`. + fn layoutable_type_sized() -> SizedType + where + Self: GpuSized, + { + match Self::layoutable_type() { + LayoutableType::Sized(s) => s, + LayoutableType::RuntimeSizedArray(_) | LayoutableType::UnsizedStruct(_) => { + unreachable!("Self is GpuSized, which these LayoutableType variants aren't.") + } + } + } } // Conversions to ScalarType, SizedType and LayoutableType // diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 4d065e3..b5ba9f8 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -12,7 +12,7 @@ use super::{ AsAny, GpuType, To, ToGpuType, }; use crate::{ - any::layout::{self, Layoutable, LayoutableSized}, + any::layout::{self, Layoutable}, call_info, common::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, @@ -574,20 +574,14 @@ impl GpuStore for vec { } -impl LayoutableSized for vec +impl Layoutable for vec where vec: NoBools, { - fn layoutable_type_sized() -> layout::SizedType { + fn layoutable_type() -> layout::LayoutableType { layout::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() } } -impl Layoutable for vec -where - vec: NoBools, -{ - fn layoutable_type() -> layout::LayoutableType { Self::layoutable_type_sized().into() } -} impl GpuLayout for vec where diff --git a/shame/src/lib.rs b/shame/src/lib.rs index d38dc75..be3e6a7 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -483,7 +483,6 @@ pub mod any { pub use type_layout::ElementLayout; // layoutable traits pub use type_layout::layoutable::Layoutable; - pub use type_layout::layoutable::LayoutableSized; // layoutable types pub use type_layout::layoutable::LayoutableType; pub use type_layout::layoutable::UnsizedStruct; diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 9350576..cc0a7e9 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -200,54 +200,37 @@ pub fn impl_for_struct( match which_derive { WhichDerive::GpuLayout => { - let layoutable_type_fn = quote! { - let result = #re::LayoutableType::struct_from_parts( - std::stringify!(#derive_struct_ident), - [ - #(( - #re::FieldOptions::new( - std::stringify!(#field_ident), - #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), - #field_size.into(), - ), - <#field_type as #re::Layoutable>::layoutable_type() - ),)* - ] - ); - - match result { - Ok(layoutable_type) => layoutable_type, - Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), - Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), - // GpuType is not implemented for derived structs directly, so they can't be used - // as the field of another struct, instead shame::Struct has to be used, which - // only accepts sized structs. - Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), - } - }; - let impl_layoutable = quote! { impl<#generics_decl> #re::Layoutable for #derive_struct_ident<#(#idents_of_generics),*> where // These NoBools and NoHandle bounds are only for better diagnostics, Layoutable already implies them - #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::LayoutableSized,)* + #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::Layoutable + #re::GpuSized,)* #last_field_type: #re::NoBools + #re::NoHandles + #re::Layoutable, #where_clause_predicates { fn layoutable_type() -> #re::LayoutableType { - #layoutable_type_fn - } - } - - impl<#generics_decl> #re::LayoutableSized for #derive_struct_ident<#(#idents_of_generics),*> - where - #(#field_type: #re::NoBools + #re::NoHandles + #triv #re::LayoutableSized,)* - #where_clause_predicates - { - fn layoutable_type_sized() -> #re::SizedType { - match { #layoutable_type_fn } { - #re::LayoutableType::Sized(s) => s, - _ => unreachable!("ensured by LayoutableSized field trait bounds above") + let result = #re::LayoutableType::struct_from_parts( + std::stringify!(#derive_struct_ident), + [ + #(( + #re::FieldOptions::new( + std::stringify!(#field_ident), + #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), + #field_size.into(), + ), + <#field_type as #re::Layoutable>::layoutable_type() + ),)* + ] + ); + + match result { + Ok(layoutable_type) => layoutable_type, + Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), + Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), + // GpuType is not implemented for derived structs directly, so they can't be used + // as the field of another struct, instead shame::Struct has to be used, which + // only accepts sized structs. + Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), } } } @@ -256,7 +239,7 @@ pub fn impl_for_struct( let impl_gpu_layout = quote! { impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> where - #(#first_fields_type: #re::LayoutableSized,)* + #(#first_fields_type: #re::Layoutable + #re::GpuSized,)* #last_field_type: #re::Layoutable, #where_clause_predicates { From 9916bd07b341c8a5edf3fa66a1da5021ad4db2aa Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:20:50 +0200 Subject: [PATCH 105/182] . --- shame/src/ir/ir_type/tensor.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 9783eaf..254a98b 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -1,9 +1,9 @@ use std::{fmt::Display, num::NonZeroU32}; use crate::{ - any::U32PowerOf2, + any::{U32PowerOf2}, common::floating_point::{f16, f32_eq_where_nans_are_equal, f64_eq_where_nans_are_equal}, - frontend::rust_types::type_layout::{self, layoutable::align_size::PACKED_ALIGN}, + frontend::rust_types::type_layout::{self, layoutable::align_size::PACKED_ALIGN, Repr}, ir::Comp4, }; @@ -530,9 +530,10 @@ impl PackedVector { } } - pub fn align(&self, repr: type_layout::Repr) -> U32PowerOf2 { - if repr.is_packed() { - return PACKED_ALIGN; + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + match repr { + Repr::Packed => return PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} } let align = match self.byte_size() { From c5979299e6af3b8ba25946f33efff19c5c994ff2 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:24:28 +0200 Subject: [PATCH 106/182] . --- shame/src/ir/pipeline/wip_pipeline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 3aa5adb..d55df5f 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -355,7 +355,7 @@ impl WipPushConstantsField { // TODO(release) the `.expect()` calls here can be removed by building a `std::alloc::Layout`-like builder for struct layouts. let sized_struct: layoutable::SizedStruct = sized_struct .try_into() - .expect("push constants are NoBools and NoHandles"); + .map_err(|e| InternalError::new(true, format!("{e}")))?; let layout = TypeLayout::new_layout_for(&sized_struct.into(), Repr::Storage); let layout = match &layout.kind { type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, From 5136b450b91348a2d155ab1866d80cdf824e3127 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:26:49 +0200 Subject: [PATCH 107/182] . --- shame/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/shame/src/lib.rs b/shame/src/lib.rs index be3e6a7..d9a0883 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -508,9 +508,6 @@ pub mod any { pub use type_layout::layoutable::FieldOptions; // layout calculation utility pub use type_layout::layoutable::LayoutCalculator; - pub use type_layout::layoutable::array_stride; - pub use type_layout::layoutable::array_size; - pub use type_layout::layoutable::array_align; pub use type_layout::layoutable::FieldOffsets; // conversion and builder errors pub use type_layout::layoutable::builder::IsUnsizedStructError; From 86e2440c0b141c4f3457aa4fd95bd764359738a8 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:27:46 +0200 Subject: [PATCH 108/182] . --- shame/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shame/src/lib.rs b/shame/src/lib.rs index d9a0883..c9d39d1 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -465,6 +465,7 @@ pub mod any { pub mod layout { use crate::frontend::rust_types::type_layout; + // type layout pub use type_layout::TypeLayout; pub use type_layout::GpuTypeLayout; @@ -481,8 +482,10 @@ pub mod any { pub use type_layout::FieldLayoutWithOffset; pub use type_layout::FieldLayout; pub use type_layout::ElementLayout; + // layoutable traits pub use type_layout::layoutable::Layoutable; + // layoutable types pub use type_layout::layoutable::LayoutableType; pub use type_layout::layoutable::UnsizedStruct; @@ -495,6 +498,7 @@ pub mod any { pub use type_layout::layoutable::Atomic; pub use type_layout::layoutable::PackedVector; pub use type_layout::layoutable::SizedStruct; + // layoutable type parts pub use type_layout::layoutable::ScalarType; pub use type_layout::layoutable::ScalarTypeFp; @@ -506,9 +510,11 @@ pub mod any { pub use type_layout::layoutable::CanonName; pub use type_layout::layoutable::SizedOrArray; pub use type_layout::layoutable::FieldOptions; + // layout calculation utility pub use type_layout::layoutable::LayoutCalculator; pub use type_layout::layoutable::FieldOffsets; + // conversion and builder errors pub use type_layout::layoutable::builder::IsUnsizedStructError; pub use type_layout::layoutable::builder::StructFromPartsError; From 3e6380217ad367146f0fdf3daead5963379e9c8d Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:30:06 +0200 Subject: [PATCH 109/182] . --- shame/tests/test_layout.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 85115ed..c5c7856 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -169,7 +169,11 @@ impl CpuLayout for f32x2_align4 { struct f32x4_align4(pub [f32; 4]); impl CpuLayout for f32x4_align4 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.align = shame::any::U32PowerOf2::_4.into(); + layout + } } #[derive(Clone, Copy)] From 09356ea41e2b8c492de5a5cb3f01d4ac4d7dd3db Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:31:51 +0200 Subject: [PATCH 110/182] . --- shame/tests/test_layout.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index c5c7856..a42e7d4 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -186,7 +186,11 @@ static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); impl CpuLayout for f32x3_align4 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.align = shame::any::U32PowerOf2::_4.into(); + layout + } } #[derive(Clone, Copy)] From c9353bad1dc02366a5f513b25cd36461486be8f5 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:32:14 +0200 Subject: [PATCH 111/182] . --- shame/tests/test_layout.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index a42e7d4..38ba59a 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -236,8 +236,6 @@ fn unsized_struct_vec3_align_layout_eq() { a: f32x4_align4, // size=16, align=4 } - println!("{}", cpu_layout::()); - // the alignment on the top level of the layout doesn't matter. // two layouts are only considered different if an alignment mismatch // leads to different offsets of fields or array elements From f02a9936fcb418b4836fb07e0826b87e4e6db02d Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:33:31 +0200 Subject: [PATCH 112/182] . --- shame/tests/test_layout.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 38ba59a..cf664fe 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -161,7 +161,11 @@ impl CpuLayout for f32x2_cpu { #[repr(C)] struct f32x2_align4(pub [f32; 2]); impl CpuLayout for f32x2_align4 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.align = shame::any::U32PowerOf2::_4.into(); + layout + } } #[derive(Clone, Copy)] From 08ec71243875a13f502872df166a9d0b70e32c8a Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:34:21 +0200 Subject: [PATCH 113/182] . --- shame/src/frontend/rust_types/vec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index b5ba9f8..06b7053 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -74,7 +74,7 @@ pub type scalar = vec; /// let rgb = my_normal.remap(-1.0..=1.0, 0.0..=1.0); // remap linear ranges (instead of " * 0.5 + 0.5") /// /// let alpha = 0.4.to_gpu(); // convert from rust to `shame` types (also works for arrays and structs) -/// let smooth: f32x1 = alpha.smootcstep(0.4..0.8); +/// let smooth: f32x1 = alpha.smoothstep(0.4..0.8); /// /// // clamp as generalized min, max, clamp via half open ranges /// let upper = alpha.clamp(..=0.8); From 4cc7b47ee068fef1f4c4aeb48481a119915ed935 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 10:34:59 +0200 Subject: [PATCH 114/182] . --- shame/src/frontend/rust_types/vec.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 06b7053..a5a2136 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -1110,7 +1110,12 @@ where Self: NoBools, { fn vertex_attrib_format() -> VertexAttribFormat { - VertexAttribFormat::Fine(L::LEN, T::SCALAR_TYPE.try_into().expect("no bools vec")) + VertexAttribFormat::Fine( + L::LEN, + T::SCALAR_TYPE + .try_into() + .expect("Self: NoBools bound on impl ensures no bools"), + ) } } From 16db7622d6c73b23b644a6cb7fb5571341e5dcbf Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 11:10:00 +0200 Subject: [PATCH 115/182] . --- shame/tests/test_layout.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index cf664fe..a494cba 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -352,7 +352,11 @@ fn external_vec_type() { } impl CpuLayoutExt for glam::Vec3 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.align = sm::any::U32PowerOf2::_4.into(); + layout + } } } From a1c4ac65b8b53d4d1c9648a45817e2b66b72fe04 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 11:12:42 +0200 Subject: [PATCH 116/182] . --- shame/src/frontend/rust_types/vec.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index a5a2136..e986a6d 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -579,7 +579,13 @@ where vec: NoBools, { fn layoutable_type() -> layout::LayoutableType { - layout::Vector::new(T::SCALAR_TYPE.try_into().expect("no bools"), L::LEN).into() + layout::Vector::new( + T::SCALAR_TYPE + .try_into() + .expect("guaranteed via `NoBools` trait bound above"), + L::LEN, + ) + .into() } } From 91e1e2fa96f2d94cc50ce8bbec31868b016d8ee6 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 11:15:02 +0200 Subject: [PATCH 117/182] . --- .../rust_types/type_layout/layoutable/align_size.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 0f1d58e..f93b12f 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -290,9 +290,11 @@ impl Matrix { impl Atomic { pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } pub const fn align(&self, repr: Repr) -> U32PowerOf2 { - if repr.is_packed() { - return PACKED_ALIGN; + match repr { + Repr::Packed => return PACKED_ALIGN, + Repr::Storage | Repr::Uniform => {} } + self.scalar.as_scalar_type().align() } } From a7324861d9dc2b430acd4c0301c55316eecaf8ea Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 11:44:46 +0200 Subject: [PATCH 118/182] . --- .../frontend/rust_types/type_layout/layoutable/align_size.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index f93b12f..bac7a0b 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -260,8 +260,9 @@ pub enum MatrixMajor { impl Matrix { pub const fn byte_size(&self, repr: Repr) -> u64 { let (vec, array_len) = self.as_vector_array(); - let array_stride = array_stride(vec.align(repr), vec.byte_size(repr)); - array_size(array_stride, array_len) + // According to https://www.w3.org/TR/WGSL/#alignment-and-size + // SizeOf(matCxR) = SizeOf(array) = C × roundUp(AlignOf(vecR), SizeOf(vecR)) + array_len.get() as u64 * round_up(vec.align(repr).as_u64(), vec.byte_size(repr)) } pub const fn align(&self, repr: Repr) -> U32PowerOf2 { From 1b48501337d95fd228fc8f7d251625993d746276 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 12:10:47 +0200 Subject: [PATCH 119/182] . --- .../frontend/rust_types/type_layout/mod.rs | 56 +++++++------------ 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index ab19673..2f08877 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -49,43 +49,6 @@ pub enum TypeLayoutSemantics { /// This models only the layout, not other characteristics of the types. /// For example an `Atomic>` is treated like a regular `vec` layout wise. /// -/// ### Generic -/// -/// `TypeLayout` has a generic `T: TypeRepr`, which is used to statically guarantee that -/// it follows specific layout rules. -/// -/// The following types implementing `TypeRepr` exist and can be found in [`shame::any::repr`]: -/// -/// ``` -/// struct Storage; /// wgsl storage address space layout -/// struct Uniform; /// wgsl uniform address space layout -/// struct Packed; /// Packed layout -/// ``` -/// -/// More information on the exact details of these layout rules is available here -/// -/// https://www.w3.org/TR/WGSL/#address-space-layout-constraints -/// -/// The following method exists for creating new type layouts based on a [`LayoutableType`] -/// ``` -/// use shame as sm; -/// use sm::any::layout::Layoutable; -/// -/// let layout_type: sm::any::layout::LayoutableType = sm::f32x1::layoutable_type(); -/// let repr = Repr::Storage; // or Uniform or Packed -/// let _ = TypeLayout::new_layout_for(&layout_type, repr); -/// ``` -/// -/// The resulting layout will always follow the layout rules of the `Repr`, however, this -/// can result in layouts that are not representable in wgsl, such as the uniform layout for -/// `shame::Array`, which requires at least a 16 byte stride. The `TypeLayout` will -/// contain information for the correct minimum stride, but since wgsl does not have a custom -/// stride attribute (like `@align` or `@size` but for strides) the type layout can't be -/// translated to wgsl. -/// -/// For the above reason `TypeLayout` exists mainly for internal usage in shame and -/// [`GpuTypeLayout`] is the user interface. See it's documentation for more information. -/// /// ### Layout comparison /// /// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question @@ -114,6 +77,25 @@ pub struct TypeLayout { /// /// An instance of `TypeLayout` (which drops compile time guarantees) can be obtained via `GpuTypeLayout::layout`. /// +/// The following types implement `TypeRepr` and can be found in [`shame::any::repr`]: +/// +/// ``` +/// struct Storage; /// wgsl storage layout rules +/// struct Uniform; /// wgsl uniform layout rules +/// struct Packed; /// Packed layout +/// +/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type, +/// /// which can be used in the storage address space. +/// GpuTypeLayout +/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type, +/// /// which can be used in the uniform address space. +/// GpuTypeLayout +/// /// Can only be used in vertex buffers and is packed. +/// GpuTypeLayout +/// ``` +/// +/// ## Construction +/// /// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created /// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by /// using `TryFrom::try_from` on a `GpuTypeLayout`, which only succeeds if From 1bcda706576c9a211e1b21244f32fbe5ac47ab8c Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 12:15:56 +0200 Subject: [PATCH 120/182] . --- shame/src/frontend/rust_types/type_layout/construction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 90c9afe..5607336 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -436,7 +436,7 @@ impl Display for StructFieldOffsetError { writeln!( f, - "The type `{top_level_type}` cannot be used as a {} buffer binding.", + "The type `{top_level_type}` cannot be layed out according to {} layout rules.", self.ctx.expected_repr )?; match is_top_level { From 590cd005703d018d0f8f0cdf59e9c68b7c07d742 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 12:19:53 +0200 Subject: [PATCH 121/182] . --- .../src/frontend/rust_types/type_layout/construction.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 5607336..7889d09 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -480,7 +480,14 @@ impl Display for StructFieldOffsetError { "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", expected_alignment, self.field_name )?; - writeln!(f, "- use a storage binding instead of a uniform binding")?; + match (self.ctx.actual_repr, self.ctx.expected_repr) { + (Repr::Storage, Repr::Uniform) => writeln!( + f, + "- use the storage address space instead of the uniform address space" + )?, + _ => {} + } + writeln!( f, "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", From 8ad86f40148c3749978d909309aa9826b6eab46b Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 12:23:03 +0200 Subject: [PATCH 122/182] . --- .../rust_types/type_layout/construction.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 7889d09..cec34ad 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -495,11 +495,15 @@ impl Display for StructFieldOffsetError { )?; writeln!(f)?; - writeln!( - f, - "In the {} address space, structs, arrays and array elements must be at least 16 byte aligned.", - self.ctx.expected_repr - )?; + match self.ctx.expected_repr { + Repr::Uniform => writeln!( + f, + "In the {} address space, structs, arrays and array elements must be at least 16 byte aligned.", + self.ctx.expected_repr + )?, + Repr::Packed | Repr::Storage => {} + } + writeln!( f, "More info about the {} address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", From d628f76cdf84442c38c1c170893854ca44abfa09 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 12:23:57 +0200 Subject: [PATCH 123/182] . --- .../rust_types/type_layout/construction.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index cec34ad..152bcef 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -504,11 +504,15 @@ impl Display for StructFieldOffsetError { Repr::Packed | Repr::Storage => {} } - writeln!( - f, - "More info about the {} address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - self.ctx.expected_repr - )?; + match self.ctx.expected_repr { + r @ (Repr::Storage | Repr::Uniform) => writeln!( + f, + "More info about the {} address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + r + )?, + Repr::Packed => {} + } + Ok(()) } } From 1dbe3ada0287cbc4d1f5be01f9326866ef2202ad Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 13:31:10 +0200 Subject: [PATCH 124/182] . --- .../rust_types/type_layout/construction.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 152bcef..694d2f6 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -175,11 +175,14 @@ fn check_repr_equivalence_for_type( use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false), }; let is_sized = matches!(ctx.top_level_type, LayoutableType::Sized(_)); - if ctx.expected_repr.is_uniform() && !is_sized { - return Err(LayoutError::UniformBufferMustBeSized( - "wgsl", - ctx.top_level_type.clone(), - )); + match (ctx.expected_repr, is_sized) { + (Repr::Uniform, false) => { + return Err(LayoutError::UniformBufferMustBeSized( + ctx.top_level_type.clone(), + Repr::Uniform, + )); + } + (Repr::Storage | Repr::Packed, _) | (Repr::Uniform, true) => {} } match &ctx.top_level_type { @@ -302,7 +305,7 @@ pub enum LayoutError { "The size of `{1}` on the gpu is not known at compile time. `{0}` \ requires that the size of uniform buffers on the gpu is known at compile time." )] - UniformBufferMustBeSized(&'static str, LayoutableType), + UniformBufferMustBeSized(LayoutableType, Repr), #[error("{0} contains a `PackedVector`, which are not allowed in {1} memory layouts ")] MayNotContainPackedVec(LayoutableType, Repr), } From 074a2063a6bc87dddd56b6a32f9a2d343631e40a Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 13:54:11 +0200 Subject: [PATCH 125/182] Fix array stride for uniform layout rules --- shame/src/frontend/any/render_io.rs | 8 +++++--- shame/src/frontend/encoding/io_iter.rs | 2 +- .../src/frontend/rust_types/layout_traits.rs | 16 +++++++++------- .../type_layout/layoutable/align_size.rs | 19 ++++++++++++++----- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 93e817d..eb72193 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,6 +2,7 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; +use crate::any::layout::{GpuTypeLayout, TypeRepr}; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::{layoutable, TypeLayout}; use crate::{ @@ -245,13 +246,14 @@ impl VertexAttribFormat { } impl Attrib { - pub(crate) fn get_attribs_and_stride( - layout: &TypeLayout, + pub(crate) fn get_attribs_and_stride( + gpu_layout: &GpuTypeLayout, mut location_counter: &LocationCounter, ) -> Option<(Box<[Attrib]>, u64)> { + let layout = gpu_layout.layout(); let stride = { let size = layout.byte_size()?; - layoutable::array_stride(layout.align(), size) + layoutable::array_stride(layout.align(), size, T::REPR) }; use TypeLayoutSemantics as TLS; diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index b31c427..7b6c2a3 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -110,7 +110,7 @@ impl VertexBuffer<'_, T> { let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { - ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); + ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout.layout()).into()); InvalidReason::ErrorThatWasPushed }); diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 5f52d72..f04e155 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -210,24 +210,26 @@ pub(crate) fn cpu_type_name_and_layout(ctx: &Context) -> Option<(C pub(crate) fn get_layout_compare_with_cpu_push_error( ctx: &Context, skip_stride_check: bool, -) -> TypeLayout { +) -> GpuTypeLayout { const ERR_COMMENT: &str = "`GpuLayout` uses WGSL layout rules unless #[gpu_repr(packed)] is used.\nsee https://www.w3.org/TR/WGSL/#structure-member-layout\n`CpuLayout` uses #[repr(C)].\nsee https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.struct"; - let gpu_layout = gpu_layout::(); + let gpu_layout = gpu_type_layout::(); if let Some((cpu_name, cpu_layout)) = cpu_type_name_and_layout::(ctx) { - check_layout_push_error(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT).ok(); + check_layout_push_error::(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT) + .ok(); } gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, cpu_layout: &TypeLayout, - gpu_layout: &TypeLayout, + gpu_layout: &GpuTypeLayout, skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { + let gpu_layout = &gpu_layout.layout(); type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { @@ -241,8 +243,8 @@ pub(crate) fn check_layout_push_error( name: gpu_layout.short_name(), }), (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = array_stride(cpu_layout.align(), cpu_size); - let gpu_stride = array_stride(gpu_layout.align(), gpu_size); + let cpu_stride = cpu_size; + let gpu_stride = array_stride(gpu_layout.align(), gpu_size, T::REPR); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index bac7a0b..faa46f7 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -308,7 +308,7 @@ impl SizedArray { pub fn byte_stride(&self, repr: Repr) -> u64 { let (element_size, element_align) = self.element.byte_size_and_align(repr); - array_stride(element_align, element_size) + array_stride(element_align, element_size, repr) } } @@ -328,9 +328,18 @@ pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 } /// Returns an array's size=>stride (the distance between consecutive elements) given the alignment and size of its elements. -pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 { - // Arrays of element type T must have an element stride that is a multiple of the - // RequiredAlignOf(T, C) for the address space C: +pub const fn array_stride(element_align: U32PowerOf2, element_size: u64, repr: Repr) -> u64 { + let element_align = match repr { + Repr::Storage => element_align, + // This should already be the case, but doesn't hurt to ensure. + Repr::Packed => PACKED_ALIGN, + // The uniform address space also requires that: + // Array elements are aligned to 16 byte boundaries. + // That is, StrideOf(array) = 16 × k’ for some positive integer k'. + // - https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), + }; + round_up(element_align.as_u64(), element_size) } @@ -338,7 +347,7 @@ pub const fn array_stride(element_align: U32PowerOf2, element_size: u64) -> u64 impl RuntimeSizedArray { pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } - pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr)) } + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr), repr) } } #[allow(missing_docs)] From 79938cac93be8a7e0ee294ceec96395837004e5d Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 14:01:02 +0200 Subject: [PATCH 126/182] Fix array stride for uniform layout rules --- examples/type_layout/src/main.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs index 6dc3256..4419ce5 100644 --- a/examples/type_layout/src/main.rs +++ b/examples/type_layout/src/main.rs @@ -1,23 +1,14 @@ #![allow(dead_code, unused)] //! Demonstration of the TypeLayout and TypeLayout Builder API. -use layout::{repr, Repr, SizedStruct}; -use shame::{ - any::{ - self, - layout::{ - self, FieldOptions, Layoutable, Len, RuntimeSizedArrayField, ScalarType, SizedField, SizedType, - UnsizedStruct, Vector, GpuTypeLayout, - }, - U32PowerOf2, - }, - boolx1, f32x1, f32x2, f32x3, f32x4, gpu_layout, Array, GpuLayout, GpuSized, TypeLayout, VertexAttribute, - VertexLayout, -}; +use shame as sm; +use shame::aliases::*; +use shame::prelude::*; +use sm::any::layout::{GpuTypeLayout, SizedStruct, Layoutable, repr}; fn main() { // We'll start by replicating this struct using `any::layout` types. - #[derive(GpuLayout)] + #[derive(sm::GpuLayout)] struct Vertex { position: f32x3, normal: f32x3, @@ -46,7 +37,7 @@ fn main() { // // Let's end on a pretty error message let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) // This has align of 4, which is ok for putting into `Storage` but `Uniform` memory requires 16 byte alignment of arrays - .extend("b", Array::>::layoutable_type_sized()); + .extend("b", sm::Array::>::layoutable_type_sized()); let storage = GpuTypeLayout::::new(sized_struct.clone()); let uniform_result = GpuTypeLayout::::try_from(storage.clone()); From ad4a86a334c1a7ce7219b225afbc941150804db2 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 14:16:09 +0200 Subject: [PATCH 127/182] Move type layout example into GpuTypeLayout docs --- examples/type_layout/Cargo.toml | 7 --- examples/type_layout/src/main.rs | 48 ------------------- .../frontend/rust_types/type_layout/mod.rs | 44 +++++++++++++++-- 3 files changed, 40 insertions(+), 59 deletions(-) delete mode 100644 examples/type_layout/Cargo.toml delete mode 100644 examples/type_layout/src/main.rs diff --git a/examples/type_layout/Cargo.toml b/examples/type_layout/Cargo.toml deleted file mode 100644 index dd64124..0000000 --- a/examples/type_layout/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "type_layout" -version = "0.1.0" -edition = "2024" - -[dependencies] -shame = { path = "../../shame/" } diff --git a/examples/type_layout/src/main.rs b/examples/type_layout/src/main.rs deleted file mode 100644 index 4419ce5..0000000 --- a/examples/type_layout/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -#![allow(dead_code, unused)] -//! Demonstration of the TypeLayout and TypeLayout Builder API. - -use shame as sm; -use shame::aliases::*; -use shame::prelude::*; -use sm::any::layout::{GpuTypeLayout, SizedStruct, Layoutable, repr}; - -fn main() { - // We'll start by replicating this struct using `any::layout` types. - #[derive(sm::GpuLayout)] - struct Vertex { - position: f32x3, - normal: f32x3, - uv: f32x2, - } - - // SizedStruct::new immediately takes the first field of the struct, because - // structs need to have at least one field. - let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) - .extend("normal", f32x3::layoutable_type_sized()) - .extend("uv", f32x1::layoutable_type_sized()); - - let storage = GpuTypeLayout::::new(sized_struct.clone()); - let packed = GpuTypeLayout::::new(sized_struct); - assert_ne!(storage.layout(), packed.layout()); - - // Does not exist: - // let uniform = GpuTypeLayout::::new(sized_struct.clone()); - - // However we can try to upgrade a GpuTypeLayout:: - let uniform = GpuTypeLayout::::try_from(storage.clone()).unwrap(); - - // Which if it succeeds, guarantees: - assert_eq!(storage.layout(), uniform.layout()); - - // // Let's end on a pretty error message - let mut sized_struct = SizedStruct::new("D", "a", f32x2::layoutable_type_sized()) - // This has align of 4, which is ok for putting into `Storage` but `Uniform` memory requires 16 byte alignment of arrays - .extend("b", sm::Array::>::layoutable_type_sized()); - - let storage = GpuTypeLayout::::new(sized_struct.clone()); - let uniform_result = GpuTypeLayout::::try_from(storage.clone()); - match uniform_result { - Err(e) => println!("This error is a showcase:\n{}", e), - Ok(u_layout) => println!("It unexpectedly worked, ohh no."), - } -} diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 2f08877..b88d9d4 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -79,28 +79,64 @@ pub struct TypeLayout { /// /// The following types implement `TypeRepr` and can be found in [`shame::any::repr`]: /// -/// ``` +/// ``` ignore /// struct Storage; /// wgsl storage layout rules /// struct Uniform; /// wgsl uniform layout rules /// struct Packed; /// Packed layout /// -/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type, +/// use shame::any::layout::GpuTypeLayout; +/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type /// /// which can be used in the storage address space. /// GpuTypeLayout -/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type, +/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type /// /// which can be used in the uniform address space. /// GpuTypeLayout /// /// Can only be used in vertex buffers and is packed. /// GpuTypeLayout /// ``` /// -/// ## Construction +/// # Construction /// /// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created /// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by /// using `TryFrom::try_from` on a `GpuTypeLayout`, which only succeeds if /// the storage layout also follows the uniform layout rules - it does not change the /// corresponding `TypeLayout`. +/// +/// # Example +/// ``` +/// use shame as sm; +/// use shame::prelude::*; +/// use shame::aliases::*; +/// use shame::any::layout::{GpuTypeLayout, SizedStruct, Layoutable, repr}; +/// +/// // We replicate this struct's `GpuLayout` using `shame::any::layout` types. +/// #[derive(sm::GpuLayout)] +/// struct Vertex { +/// position: f32x3, +/// normal: f32x3, +/// uv: f32x2, +/// } +/// +/// // SizedStruct::new immediately takes the first field of the struct, because +/// // structs need to have at least one field. +/// let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) +/// .extend("normal", f32x3::layoutable_type_sized()) +/// .extend("uv", f32x1::layoutable_type_sized()); +/// +/// let storage = GpuTypeLayout::::new(sized_struct.clone()); +/// let packed = GpuTypeLayout::::new(sized_struct); +/// assert_ne!(storage.layout(), packed.layout()); +/// +/// // Does not exist: +/// // let uniform = GpuTypeLayout::::new(sized_struct.clone()); +/// +/// // However we can try to upgrade a GpuTypeLayout:: +/// let uniform = GpuTypeLayout::::try_from(storage.clone()).unwrap(); +/// +/// // Which if it succeeds, guarantees: +/// assert_eq!(storage.layout(), uniform.layout()); +/// ``` #[derive(Debug, Clone)] pub struct GpuTypeLayout { ty: LayoutableType, From fd236270950253d3131b56264e260c3095829592 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Fri, 4 Jul 2025 14:18:29 +0200 Subject: [PATCH 128/182] . --- shame/src/frontend/rust_types/type_layout/layoutable/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index a913b13..5965c48 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -24,7 +24,8 @@ pub use builder::{SizedOrArray, FieldOptions}; /// /// `LayoutableType` does not contain any layout information itself, but a layout /// can be assigned to it using [`GpuTypeLayout`] according to one of the available layout rules: -/// storage, uniform or packed. +/// `repr::Storage`, `repr::Uniform`` or `repr::Packed`, see [`GpuTypeLayout`] documentation +/// for more details. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum LayoutableType { /// A type with a known size. From bffd8b5d9ff8c1a4ae9a4bb32034a8cfa7cdd1fc Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 6 Jul 2025 11:09:37 +0200 Subject: [PATCH 129/182] Add unit tests to align_size.rs layout calculations --- .../type_layout/layoutable/align_size.rs | 644 +++++++++++++++++- 1 file changed, 622 insertions(+), 22 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index faa46f7..efd7c76 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -71,7 +71,6 @@ impl SizedType { } } - impl SizedStruct { /// Returns [`FieldOffsetsSized`], which serves as an iterator over the offsets of the /// fields of this struct. `FieldOffsetsSized::struct_byte_size_and_align` can be @@ -125,7 +124,9 @@ impl<'a> FieldOffsets<'a> { pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); impl Iterator for FieldOffsetsSized<'_> { type Item = u64; - fn next(&mut self) -> Option { self.0.next() } + fn next(&mut self) -> Option { + self.0.next() + } } impl<'a> FieldOffsetsSized<'a> { /// Consumes self and calculates the byte size and align of a struct @@ -141,7 +142,9 @@ impl<'a> FieldOffsetsSized<'a> { } /// Returns the inner iterator over sized fields. - pub fn into_inner(self) -> FieldOffsets<'a> { self.0 } + pub fn into_inner(self) -> FieldOffsets<'a> { + self.0 + } } /// The field offsets of an `UnsizedStruct`. @@ -163,7 +166,9 @@ impl<'a> FieldOffsetsUnsized<'a> { } /// Returns an iterator over the sized field offsets. - pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { &mut self.sized } + pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { + &mut self.sized + } /// Returns the last field's offset and the struct's align. pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { @@ -177,7 +182,9 @@ impl<'a> FieldOffsetsUnsized<'a> { } /// Returns the inner iterator over sized fields. - pub fn into_inner(self) -> FieldOffsets<'a> { self.sized } + pub fn into_inner(self) -> FieldOffsets<'a> { + self.sized + } } impl UnsizedStruct { @@ -191,7 +198,9 @@ impl UnsizedStruct { } /// This is expensive as it calculates the byte align by traversing all fields recursively. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + self.field_offsets(repr).last_field_offset_and_struct_align().1 + } } const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { @@ -205,7 +214,9 @@ const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32 #[allow(missing_docs)] impl Vector { - pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } + pub const fn new(scalar: ScalarType, len: Len) -> Self { + Self { scalar, len } + } pub const fn byte_size(&self, repr: Repr) -> u64 { match repr { @@ -221,7 +232,7 @@ impl Vector { Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), Len::X3 => 4, }; - let po2_align = self.scalar.align(); + let po2_align = self.scalar.align(repr); U32PowerOf2::try_from_u32(po2_len * po2_align.as_u32()).expect( "power of 2 * power of 2 = power of 2. Highest operands are around 4 * 16 so overflow is unlikely", ) @@ -240,11 +251,14 @@ impl ScalarType { } } - pub const fn align(&self) -> U32PowerOf2 { - match self { - ScalarType::F16 => U32PowerOf2::_2, - ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, - ScalarType::F64 => U32PowerOf2::_8, + pub const fn align(&self, repr: Repr) -> U32PowerOf2 { + match repr { + Repr::Packed => return PACKED_ALIGN, + Repr::Storage | Repr::Uniform => match self { + ScalarType::F16 => U32PowerOf2::_2, + ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, + ScalarType::F64 => U32PowerOf2::_8, + }, } } } @@ -289,22 +303,28 @@ impl Matrix { #[allow(missing_docs)] impl Atomic { - pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } + pub const fn byte_size(&self) -> u64 { + self.scalar.as_scalar_type().byte_size() + } pub const fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { Repr::Packed => return PACKED_ALIGN, Repr::Storage | Repr::Uniform => {} } - self.scalar.as_scalar_type().align() + self.scalar.as_scalar_type().align(repr) } } #[allow(missing_docs)] impl SizedArray { - pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } + pub fn byte_size(&self, repr: Repr) -> u64 { + array_size(self.byte_stride(repr), self.len) + } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + array_align(self.element.align(repr), repr) + } pub fn byte_stride(&self, repr: Repr) -> u64 { let (element_size, element_align) = self.element.byte_size_and_align(repr); @@ -315,7 +335,9 @@ impl SizedArray { /// Returns an array's size given it's stride and length. /// /// Note, this is independent of layout rules (`Repr`). -pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } +pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { + array_stride * len.get() as u64 +} /// Returns an array's size given the alignment of it's elements. pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { @@ -345,9 +367,13 @@ pub const fn array_stride(element_align: U32PowerOf2, element_size: u64, repr: R #[allow(missing_docs)] impl RuntimeSizedArray { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { + array_align(self.element.align(repr), repr) + } - pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr), repr) } + pub fn byte_stride(&self, repr: Repr) -> u64 { + array_stride(self.align(repr), self.element.byte_size(repr), repr) + } } #[allow(missing_docs)] @@ -480,10 +506,14 @@ impl LayoutCalculator { // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) // // self.next_offset_min is justPastLastMember already. - pub const fn byte_size(&self) -> u64 { round_up(self.align.as_u64(), self.next_offset_min) } + pub const fn byte_size(&self) -> u64 { + round_up(self.align.as_u64(), self.next_offset_min) + } /// Returns the align of the struct. - pub const fn align(&self) -> U32PowerOf2 { self.align } + pub const fn align(&self) -> U32PowerOf2 { + self.align + } const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { let field_align = Self::calculate_align(field_align, field_custom_min_align); @@ -513,3 +543,573 @@ impl LayoutCalculator { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::any::U32PowerOf2; + use crate::frontend::rust_types::type_layout::Repr; + use crate::ir::{Len, Len2, ScalarTypeFp, ScalarTypeInteger}; + use std::num::NonZeroU32; + use std::rc::Rc; + use super::super::builder::FieldOptions; + + #[test] + fn test_primitives_layout() { + // Testing all aligns and sizes found here (and some more that aren't in the spec like f64) + // https://www.w3.org/TR/WGSL/#alignment-and-size + + // i32, u32, or f32: AlilgnOf(T) = 4, SizeOf(T) = 4 + assert_eq!(ScalarType::I32.byte_size(), 4); + assert_eq!(ScalarType::I32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(ScalarType::U32.byte_size(), 4); + assert_eq!(ScalarType::U32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(ScalarType::F32.byte_size(), 4); + assert_eq!(ScalarType::F32.align(Repr::Storage), U32PowerOf2::_4); + // f16: AlilgnOf(T) = 2, SizeOf(T) = 2 + assert_eq!(ScalarType::F16.byte_size(), 2); + assert_eq!(ScalarType::F16.align(Repr::Storage), U32PowerOf2::_2); + // not found in spec + assert_eq!(ScalarType::F64.byte_size(), 8); + assert_eq!(ScalarType::F64.align(Repr::Storage), U32PowerOf2::_8); + + // Test atomics + let atomic_u32 = Atomic { + scalar: ScalarTypeInteger::U32, + }; + let atomic_i32 = Atomic { + scalar: ScalarTypeInteger::I32, + }; + // atomic: AlignOf(T) = 4, SizeOf(T) = 4 + assert_eq!(atomic_u32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(atomic_u32.byte_size(), 4); + assert_eq!(atomic_i32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(atomic_i32.byte_size(), 4); + + // Test vectors + let vec2_f32 = Vector::new(ScalarType::F32, Len::X2); + let vec2_f16 = Vector::new(ScalarType::F16, Len::X2); + let vec3_f32 = Vector::new(ScalarType::F32, Len::X3); + let vec3_f16 = Vector::new(ScalarType::F16, Len::X3); + let vec4_f32 = Vector::new(ScalarType::F32, Len::X4); + let vec4_f16 = Vector::new(ScalarType::F16, Len::X4); + // vec2, T is i32, u32, or f32: AlignOf(T) = 8, SizeOf(T) = 8 + assert_eq!(vec2_f32.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(vec2_f32.byte_size(Repr::Storage), 8); + // vec2: AlignOf(T) = 4, SizeOf(T) = 4 + assert_eq!(vec2_f16.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(vec2_f16.byte_size(Repr::Storage), 4); + // vec3, T is i32, u32, or f32: AlignOf(T) = 16, SizeOf(T) = 12 + assert_eq!(vec3_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(vec3_f32.byte_size(Repr::Storage), 12); + // vec3: AlignOf(T) = 8, SizeOf(T) = 6 + assert_eq!(vec3_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(vec3_f16.byte_size(Repr::Storage), 6); + // vec4, T is i32, u32, or f32: AlignOf(T) = 16, SizeOf(T) = 16 + assert_eq!(vec4_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(vec4_f32.byte_size(Repr::Storage), 16); + // vec4: AlignOf(T) = 8, SizeOf(T) = 8 + assert_eq!(vec4_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(vec4_f16.byte_size(Repr::Storage), 8); + + // Test matrices + let mat2x2_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X2, + rows: Len2::X2, + }; + let mat2x2_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X2, + rows: Len2::X2, + }; + let mat3x2_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X3, + rows: Len2::X2, + }; + let mat3x2_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X3, + rows: Len2::X2, + }; + let mat4x2_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X4, + rows: Len2::X2, + }; + let mat4x2_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X4, + rows: Len2::X2, + }; + let mat2x3_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X2, + rows: Len2::X3, + }; + let mat2x3_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X2, + rows: Len2::X3, + }; + let mat3x3_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X3, + rows: Len2::X3, + }; + let mat3x3_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X3, + rows: Len2::X3, + }; + let mat4x3_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X4, + rows: Len2::X3, + }; + let mat4x3_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X4, + rows: Len2::X3, + }; + let mat2x4_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X2, + rows: Len2::X4, + }; + let mat2x4_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X2, + rows: Len2::X4, + }; + let mat3x4_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X3, + rows: Len2::X4, + }; + let mat3x4_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X3, + rows: Len2::X4, + }; + let mat4x4_f32 = Matrix { + scalar: ScalarTypeFp::F32, + columns: Len2::X4, + rows: Len2::X4, + }; + let mat4x4_f16 = Matrix { + scalar: ScalarTypeFp::F16, + columns: Len2::X4, + rows: Len2::X4, + }; + // mat2x2: AlignOf(T) = 8, SizeOf(T) = 16 + assert_eq!(mat2x2_f32.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat2x2_f32.byte_size(Repr::Storage), 16); + // mat2x2: AlignOf(T) = 4, SizeOf(T) = 8 + assert_eq!(mat2x2_f16.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(mat2x2_f16.byte_size(Repr::Storage), 8); + // mat3x2: AlignOf(T) = 8, SizeOf(T) = 24 + assert_eq!(mat3x2_f32.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat3x2_f32.byte_size(Repr::Storage), 24); + // mat3x2: AlignOf(T) = 4, SizeOf(T) = 12 + assert_eq!(mat3x2_f16.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(mat3x2_f16.byte_size(Repr::Storage), 12); + // mat4x2: AlignOf(T) = 8, SizeOf(T) = 32 + assert_eq!(mat4x2_f32.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat4x2_f32.byte_size(Repr::Storage), 32); + // mat4x2: AlignOf(T) = 4, SizeOf(T) = 16 + assert_eq!(mat4x2_f16.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(mat4x2_f16.byte_size(Repr::Storage), 16); + // mat2x3: AlignOf(T) = 16, SizeOf(T) = 32 + assert_eq!(mat2x3_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(mat2x3_f32.byte_size(Repr::Storage), 32); + // mat2x3: AlignOf(T) = 8, SizeOf(T) = 16 + assert_eq!(mat2x3_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat2x3_f16.byte_size(Repr::Storage), 16); + // mat3x3: AlignOf(T) = 16, SizeOf(T) = 48 + assert_eq!(mat3x3_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(mat3x3_f32.byte_size(Repr::Storage), 48); + // mat3x3: AlignOf(T) = 8, SizeOf(T) = 24 + assert_eq!(mat3x3_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat3x3_f16.byte_size(Repr::Storage), 24); + // mat4x3: AlignOf(T) = 16, SizeOf(T) = 64 + assert_eq!(mat4x3_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(mat4x3_f32.byte_size(Repr::Storage), 64); + // mat4x3: AlignOf(T) = 8, SizeOf(T) = 32 + assert_eq!(mat4x3_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat4x3_f16.byte_size(Repr::Storage), 32); + // mat2x4: AlignOf(T) = 16, SizeOf(T) = 32 + assert_eq!(mat2x4_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(mat2x4_f32.byte_size(Repr::Storage), 32); + // mat2x4: AlignOf(T) = 8, SizeOf(T) = 16 + assert_eq!(mat2x4_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat2x4_f16.byte_size(Repr::Storage), 16); + // mat3x4: AlignOf(T) = 16, SizeOf(T) = 48 + assert_eq!(mat3x4_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(mat3x4_f32.byte_size(Repr::Storage), 48); + // mat3x4: AlignOf(T) = 8, SizeOf(T) = 24 + assert_eq!(mat3x4_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat3x4_f16.byte_size(Repr::Storage), 24); + // mat4x4: AlignOf(T) = 16, SizeOf(T) = 64 + assert_eq!(mat4x4_f32.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(mat4x4_f32.byte_size(Repr::Storage), 64); + // mat4x4: AlignOf(T) = 8, SizeOf(T) = 32 + assert_eq!(mat4x4_f16.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(mat4x4_f16.byte_size(Repr::Storage), 32); + + // Testing Repr::Uniform and Repr::Packed // + + let scalars = [ + ScalarType::F16, + ScalarType::F32, + ScalarType::F64, + ScalarType::I32, + ScalarType::U32, + ]; + let atomics = [atomic_u32, atomic_i32]; + let vectors = [vec2_f32, vec2_f16, vec3_f32, vec3_f16, vec4_f32, vec4_f16]; + let matrices = [ + mat2x2_f32, mat2x2_f16, mat3x2_f32, mat3x2_f16, mat4x2_f32, mat4x2_f16, mat2x3_f32, mat2x3_f16, mat3x3_f32, + mat3x3_f16, mat4x3_f32, mat4x3_f16, mat2x4_f32, mat2x4_f16, mat3x4_f32, mat3x4_f16, mat4x4_f32, mat4x4_f16, + ]; + + // Testing + // - byte size for Storage, Uniform and Packed is the same + // - align of Storage and Uniform is the same + // - align of Packed is 1 + for scalar in scalars { + // Looks silly, because byte_size doesn't have a repr argument. + assert_eq!(scalar.byte_size(), scalar.byte_size()); + assert_eq!(scalar.align(Repr::Storage), scalar.align(Repr::Uniform)); + assert_eq!(scalar.align(Repr::Packed), U32PowerOf2::_1); + } + for atomic in atomics { + // Looks silly, because byte_size doesn't have a repr argument. + assert_eq!(atomic.byte_size(), atomic.byte_size()); + assert_eq!(atomic.align(Repr::Storage), atomic.align(Repr::Uniform)); + assert_eq!(atomic.align(Repr::Packed), U32PowerOf2::_1); + } + for vector in vectors { + assert_eq!(vector.byte_size(Repr::Storage), vector.byte_size(Repr::Uniform)); + assert_eq!(vector.align(Repr::Storage), vector.align(Repr::Uniform)); + assert_eq!(vector.align(Repr::Packed), U32PowerOf2::_1); + } + for matrix in matrices { + assert_eq!(matrix.byte_size(Repr::Storage), matrix.byte_size(Repr::Uniform)); + assert_eq!(matrix.align(Repr::Storage), matrix.align(Repr::Uniform)); + assert_eq!(matrix.align(Repr::Packed), U32PowerOf2::_1); + } + } + + #[test] + fn test_sized_array_layout() { + let element = SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)); + let array = SizedArray { + element: Rc::new(element), + len: NonZeroU32::new(5).unwrap(), + }; + + // vec2 is 8 bytes, aligned to 8 bytes + assert_eq!(array.byte_stride(Repr::Storage), 8); + assert_eq!(array.byte_size(Repr::Storage), 40); // 5 * 8 + assert_eq!(array.align(Repr::Storage), U32PowerOf2::_8); + + // Uniform requires 16-byte alignment for array elements + assert_eq!(array.byte_stride(Repr::Uniform), 16); + assert_eq!(array.byte_size(Repr::Uniform), 80); // 5 * 16 + assert_eq!(array.align(Repr::Uniform), U32PowerOf2::_16); + + // Packed has 1-byte alignment + assert_eq!(array.byte_stride(Repr::Packed), 8); + assert_eq!(array.byte_size(Repr::Packed), 40); // 5 * 8 + assert_eq!(array.align(Repr::Packed), U32PowerOf2::_1); + } + + #[test] + fn test_runtime_sized_array_layout() { + let element = SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)); + let array = RuntimeSizedArray { element }; + + assert_eq!(array.byte_stride(Repr::Storage), 8); + assert_eq!(array.align(Repr::Storage), U32PowerOf2::_8); + + assert_eq!(array.byte_stride(Repr::Uniform), 16); + assert_eq!(array.align(Repr::Uniform), U32PowerOf2::_16); + + assert_eq!(array.byte_stride(Repr::Packed), 8); + assert_eq!(array.align(Repr::Packed), U32PowerOf2::_1); + } + + #[test] + fn test_array_size() { + let len = NonZeroU32::new(5).unwrap(); + assert_eq!(array_size(8, len), 40); + assert_eq!(array_size(16, len), 80); + assert_eq!(array_size(1, len), 5); + } + + #[test] + fn test_array_align() { + let element_align = U32PowerOf2::_8; + + assert_eq!(array_align(element_align, Repr::Storage), U32PowerOf2::_8); + assert_eq!(array_align(element_align, Repr::Uniform), U32PowerOf2::_16); + assert_eq!(array_align(element_align, Repr::Packed), U32PowerOf2::_1); + + // Test with smaller alignment + let small_align = U32PowerOf2::_4; + assert_eq!(array_align(small_align, Repr::Storage), U32PowerOf2::_4); + assert_eq!(array_align(small_align, Repr::Uniform), U32PowerOf2::_16); + assert_eq!(array_align(small_align, Repr::Packed), U32PowerOf2::_1); + } + + #[test] + fn test_array_stride() { + let element_align = U32PowerOf2::_8; + let element_size = 12; + + // Storage: round up to element alignment + assert_eq!(array_stride(element_align, element_size, Repr::Storage), 16); + // Uniform: round up to 16-byte alignment + assert_eq!(array_stride(element_align, element_size, Repr::Uniform), 16); + // Packed: round up to 1-byte alignment (no padding) + assert_eq!(array_stride(element_align, element_size, Repr::Packed), 12); + } + + #[test] + fn test_layout_calculator_basic() { + let mut calc = LayoutCalculator::new(Repr::Storage); + + // Add a u32 field + let offset1 = calc.extend(4, U32PowerOf2::_4, None, None, false); + assert_eq!(offset1, 0); + // Add another u32 field + let offset2 = calc.extend(4, U32PowerOf2::_4, None, None, false); + assert_eq!(offset2, 4); + // Add a vec2 field (8 bytes, 8-byte aligned) + let offset3 = calc.extend(8, U32PowerOf2::_8, None, None, false); + assert_eq!(offset3, 8); + + assert_eq!(calc.byte_size(), 16); + assert_eq!(calc.align(), U32PowerOf2::_8); + } + + #[test] + fn test_layout_calculator_packed() { + let mut calc = LayoutCalculator::new(Repr::Packed); + + // Add a u32 field - should be packed without padding + let offset1 = calc.extend(4, U32PowerOf2::_4, None, None, false); + assert_eq!(offset1, 0); + + // Add a vec2 field - should be packed directly after + let offset2 = calc.extend(8, U32PowerOf2::_8, None, None, false); + assert_eq!(offset2, 4); + + assert_eq!(calc.byte_size(), 12); + assert_eq!(calc.align(), U32PowerOf2::_1); + + // Add a vec2 field - but with custom min align, which overwrites paacked alignment + let offset3 = calc.extend(8, U32PowerOf2::_8, None, Some(U32PowerOf2::_16), false); + assert_eq!(offset3, 16); + // TODO(chronicl) not sure whether the alignment shouldn't stay 1 for a packesd struct + // with custom min align field. + assert_eq!(calc.align(), U32PowerOf2::_16); + } + + #[test] + fn test_layout_calculator_uniform_struct_padding() { + let mut calc = LayoutCalculator::new(Repr::Uniform); + + // Add a nested struct with size 12 + let offset1 = calc.extend(12, U32PowerOf2::_4, None, None, true); + assert_eq!(offset1, 0); + // Add another field - should be padded to 16-byte boundary from struct + let offset2 = calc.extend(4, U32PowerOf2::_4, None, None, false); + assert_eq!(offset2, 16); + + assert_eq!(calc.byte_size(), 20); + // TODO(chronicl) make this pass + assert_eq!(calc.align(), U32PowerOf2::_16); // Uniform struct alignment is multipole of 16 + } + + #[test] + fn test_layout_calculator_custom_sizes_and_aligns() { + let mut calc = LayoutCalculator::new(Repr::Storage); + + // Add field with custom minimum size + let offset1 = calc.extend(4, U32PowerOf2::_4, Some(33), None, false); + assert_eq!(offset1, 0); + assert_eq!(calc.byte_size(), 36); // 33 rounded up to multiple of align + + // Add field with custom minimum alignment + let offset2 = calc.extend(4, U32PowerOf2::_4, None, Some(U32PowerOf2::_16), false); + assert_eq!(offset2, 48); + + // 33 -> placed at 48 due to 16 align -> 64 size because rounded up to multiple of align + assert_eq!(calc.byte_size(), 64); + assert_eq!(calc.align(), U32PowerOf2::_16); + } + + #[test] + fn test_layout_calculator_extend_unsized() { + let mut calc = LayoutCalculator::new(Repr::Storage); + + // Add some sized fields first + calc.extend(4, U32PowerOf2::_4, None, None, false); + calc.extend(8, U32PowerOf2::_8, None, None, false); + + // Add unsized field + let (offset, align) = calc.extend_unsized(U32PowerOf2::_4, None); + assert_eq!(offset, 16); + assert_eq!(align, U32PowerOf2::_8); + } + + #[test] + fn test_sized_field_calculations() { + let field = SizedField::new( + FieldOptions::new("test_field", None, Some(16)), + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), + ); + + // Vector is 8 bytes, but field has custom min size of 16 + assert_eq!(field.byte_size(Repr::Storage), 16); + assert_eq!(field.align(Repr::Storage), U32PowerOf2::_8); + + // Test custom alignment + let field2 = SizedField::new( + FieldOptions::new("test_field2", Some(U32PowerOf2::_16), None), + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), + ); + + assert_eq!(field2.align(Repr::Storage), U32PowerOf2::_16); + } + + #[test] + fn test_runtime_sized_array_field_align() { + let field = RuntimeSizedArrayField::new( + "test_array", + Some(U32PowerOf2::_16), + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), + ); + + // Array has 8-byte alignment, but field has custom min align of 16 + assert_eq!(field.align(Repr::Storage), U32PowerOf2::_16); + // Test packed representation with custom alignment + assert_eq!(field.align(Repr::Packed), U32PowerOf2::_16); + } + + #[test] + fn test_complex_struct_layout() { + // Create a struct with mixed field types + let sized_struct = SizedStruct::new( + "TestStruct", + "field1", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes, 4-byte aligned + ) + .extend( + "field2", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 8 bytes, 8-byte aligned + ) + .extend( + "field3", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes, 4-byte aligned + ); + + // Test field offsets + let mut field_offsets = sized_struct.field_offsets(Repr::Storage); + + // Field 1: offset 0 + assert_eq!(field_offsets.next(), Some(0)); + // Field 2: offset 8 (aligned to 8-byte boundary) + assert_eq!(field_offsets.next(), Some(8)); + // Field 3: offset 16 (directly after field 2) + assert_eq!(field_offsets.next(), Some(16)); + // No more fields + assert_eq!(field_offsets.next(), None); + + // Test struct size and alignment + let (size, align) = sized_struct.byte_size_and_align(Repr::Storage); + assert_eq!(size, 24); // Round up to 8-byte alignment: round_up(8, 20) = 24 + assert_eq!(align, U32PowerOf2::_8); + } + + #[test] + fn test_uniform_struct_alignment() { + let sized_struct = SizedStruct::new( + "TestStruct", + "field1", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 8 bytes, 8-byte aligned + ); + + let (size, align) = sized_struct.byte_size_and_align(Repr::Uniform); + + // TODO(chronicl) this shouldn't be 8, it should be 16, because struct size is + // roundUp(AlignOf(S), justPastLastMember) where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) + assert_eq!(size, 8); // Size calculated with original 8-byte alignment + assert_eq!(align, U32PowerOf2::_16); // Alignment adjusted for uniform + } + + #[test] + fn test_unsized_struct_layout() { + // Test UnsizedStruct with sized fields and a runtime sized array + let unsized_struct = SizedStruct::new( + "UnsizedStruct", + "field1", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 4 bytes, 4-byte aligned + ) + .extend( + "field2", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 8 bytes, 8-byte aligned + ) + .extend_unsized( + "runtime_array", + None, + SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes per element + ); + + // Test field offsets + let mut field_offsets = unsized_struct.field_offsets(Repr::Storage); + let sized_offsets: Vec = field_offsets.sized_field_offsets().collect(); + assert_eq!(sized_offsets, vec![0, 8]); // First field at 0, second at 8 + + // Test last field offset and struct alignment + let (last_offset, struct_align) = field_offsets.last_field_offset_and_struct_align(); + assert_eq!(last_offset, 12); // Runtime array starts at offset 12 + assert_eq!(struct_align, U32PowerOf2::_8); // Struct alignment is 8 + + // Test struct alignment method + assert_eq!(unsized_struct.align(Repr::Storage), U32PowerOf2::_8); + + // Test with different repr + let mut field_offsets_uniform = unsized_struct.field_offsets(Repr::Uniform); + let (last_offset_uniform, struct_align_uniform) = field_offsets_uniform.last_field_offset_and_struct_align(); + assert_eq!(last_offset_uniform, 16); // Different offset in uniform, because array's alignment is 16 + assert_eq!(struct_align_uniform, U32PowerOf2::_16); // Uniform struct alignment + } + + #[test] + fn test_packed_struct_layout() { + let sized_struct = SizedStruct::new( + "TestStruct", + "field1", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X3)), // 8 bytes, 16 align (when not packed) + ) + .extend( + "field2", + SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes + ); + + let mut field_offsets = sized_struct.field_offsets(Repr::Packed); + + // Field 1: offset 0 + assert_eq!(field_offsets.next(), Some(0)); + // Field 2: offset 12, packed directly after field 1, despite 16 alignment of field1, because packed + assert_eq!(field_offsets.next(), Some(12)); + + let (size, align) = sized_struct.byte_size_and_align(Repr::Packed); + assert_eq!(size, 16); + assert_eq!(align, U32PowerOf2::_1); + } +} From a35b60278961edc79233d41d366575b3a9de7e65 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 6 Jul 2025 11:16:52 +0200 Subject: [PATCH 130/182] Move adjust_struct_alignment_for_repr into LayoutCalculator --- .../type_layout/layoutable/align_size.rs | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index efd7c76..11f4d11 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -135,10 +135,7 @@ impl<'a> FieldOffsetsSized<'a> { // Finishing layout calculations // using count only to advance iterator to the end (&mut self.0).count(); - ( - self.0.calc.byte_size(), - adjust_struct_alignment_for_repr(self.0.calc.align(), self.0.repr), - ) + (self.0.calc.byte_size(), self.0.calc.align()) } /// Returns the inner iterator over sized fields. @@ -177,8 +174,7 @@ impl<'a> FieldOffsetsUnsized<'a> { (&mut self.sized).count(); let array_align = self.last_unsized.array.align(self.sized.repr); let custom_min_align = self.last_unsized.custom_min_align; - let (offset, align) = self.sized.calc.extend_unsized(array_align, custom_min_align); - (offset, adjust_struct_alignment_for_repr(align, self.sized.repr)) + self.sized.calc.extend_unsized(array_align, custom_min_align) } /// Returns the inner iterator over sized fields. @@ -203,15 +199,6 @@ impl UnsizedStruct { } } -const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { - match repr { - // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage => align, - Repr::Uniform => round_up_align(U32PowerOf2::_16, align), - Repr::Packed => PACKED_ALIGN, - } -} - #[allow(missing_docs)] impl Vector { pub const fn new(scalar: ScalarType, len: Len) -> Self { @@ -497,7 +484,7 @@ impl LayoutCalculator { let offset = self.next_field_offset(align, custom_min_align); self.align = self.align.max(align); - (offset, self.align) + (offset, self.align()) } /// Returns the byte size of the struct. @@ -507,12 +494,12 @@ impl LayoutCalculator { // // self.next_offset_min is justPastLastMember already. pub const fn byte_size(&self) -> u64 { - round_up(self.align.as_u64(), self.next_offset_min) + round_up(self.align().as_u64(), self.next_offset_min) } /// Returns the align of the struct. pub const fn align(&self) -> U32PowerOf2 { - self.align + Self::adjust_struct_alignment_for_repr(self.align, self.repr) } const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { @@ -542,6 +529,15 @@ impl LayoutCalculator { align } } + + const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { + match repr { + // Packedness is ensured by the `LayoutCalculator`. + Repr::Storage => align, + Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + Repr::Packed => PACKED_ALIGN, + } + } } #[cfg(test)] @@ -913,9 +909,9 @@ mod tests { // Add a vec2 field - but with custom min align, which overwrites paacked alignment let offset3 = calc.extend(8, U32PowerOf2::_8, None, Some(U32PowerOf2::_16), false); assert_eq!(offset3, 16); - // TODO(chronicl) not sure whether the alignment shouldn't stay 1 for a packesd struct + // TODO(chronicl) not sure whether the alignment should stay 1 for a packesd struct // with custom min align field. - assert_eq!(calc.align(), U32PowerOf2::_16); + assert_eq!(calc.align(), U32PowerOf2::_1); } #[test] @@ -929,9 +925,8 @@ mod tests { let offset2 = calc.extend(4, U32PowerOf2::_4, None, None, false); assert_eq!(offset2, 16); - assert_eq!(calc.byte_size(), 20); - // TODO(chronicl) make this pass - assert_eq!(calc.align(), U32PowerOf2::_16); // Uniform struct alignment is multipole of 16 + assert_eq!(calc.align(), U32PowerOf2::_16); // Uniform struct alignment is multiple of 16 + assert_eq!(calc.byte_size(), 32); // Byte size of struct is a multiple of it's align } #[test] @@ -1045,10 +1040,8 @@ mod tests { let (size, align) = sized_struct.byte_size_and_align(Repr::Uniform); - // TODO(chronicl) this shouldn't be 8, it should be 16, because struct size is - // roundUp(AlignOf(S), justPastLastMember) where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) - assert_eq!(size, 8); // Size calculated with original 8-byte alignment assert_eq!(align, U32PowerOf2::_16); // Alignment adjusted for uniform + assert_eq!(size, 16); // Byte size of struct is a multiple of it's alignment } #[test] From ddf63799bfb562f0ad5f5c83a20aada5adad34b4 Mon Sep 17 00:00:00 2001 From: chronicl <_> Date: Sun, 6 Jul 2025 22:19:07 +0200 Subject: [PATCH 131/182] Add unit tests for TypeLayout::new_layout_for --- .../rust_types/type_layout/construction.rs | 225 +++++++++++++++++- 1 file changed, 219 insertions(+), 6 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 694d2f6..4829021 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -310,7 +310,6 @@ pub enum LayoutError { MayNotContainPackedVec(LayoutableType, Repr), } - #[derive(Debug, Clone, Copy)] pub struct LayoutContext<'a> { top_level_type: &'a LayoutableType, @@ -339,7 +338,9 @@ impl From> for LayoutErrorContext { } impl Repr { - fn more_info_at(&self) -> &str { "https://www.w3.org/TR/WGSL/#memory-layouts" } + fn more_info_at(&self) -> &str { + "https://www.w3.org/TR/WGSL/#memory-layouts" + } } #[allow(missing_docs)] @@ -396,10 +397,14 @@ pub enum StructKind { } impl From for StructKind { - fn from(value: SizedStruct) -> Self { StructKind::Sized(value) } + fn from(value: SizedStruct) -> Self { + StructKind::Sized(value) + } } impl From for StructKind { - fn from(value: UnsizedStruct) -> Self { StructKind::Unsized(value) } + fn from(value: UnsizedStruct) -> Self { + StructKind::Unsized(value) + } } impl std::error::Error for StructFieldOffsetError {} @@ -436,7 +441,6 @@ impl Display for StructFieldOffsetError { }) .flatten(); - writeln!( f, "The type `{top_level_type}` cannot be layed out according to {} layout rules.", @@ -589,9 +593,218 @@ where } write!(f, "{:6} {:5}", last_field_offset, last_field.align(repr).as_u64())?; - reset(f, field_index)?; } writeln!(f, "}}")?; Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + any::U32PowerOf2, + frontend::rust_types::type_layout::{ + layoutable::{*}, + repr::Repr, + *, + }, + }; + use std::{rc::Rc, num::NonZeroU32}; + + #[test] + fn test_array_alignment() { + let array = SizedArray::new( + Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), + NonZeroU32::new(1).unwrap(), + ) + .into(); + + let storage = TypeLayout::new_layout_for(&array, Repr::Storage); + let uniform = TypeLayout::new_layout_for(&array, Repr::Uniform); + let packed = TypeLayout::new_layout_for(&array, Repr::Packed); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(4)); + assert_eq!(uniform.byte_size(), Some(16)); + assert_eq!(packed.byte_size(), Some(4)); + + match (storage.kind, uniform.kind, packed.kind) { + ( + TypeLayoutSemantics::Array(storage, Some(1)), + TypeLayoutSemantics::Array(uniform, Some(1)), + TypeLayoutSemantics::Array(packed, Some(1)), + ) => { + assert_eq!(storage.byte_stride, 4); + assert_eq!(uniform.byte_stride, 16); + assert_eq!(packed.byte_stride, 4); + } + _ => panic!("Unexpected layout kind"), + } + } + + #[test] + fn test_struct_alignment() { + let s = SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1)).into(); + + let storage = TypeLayout::new_layout_for(&s, Repr::Storage); + let uniform = TypeLayout::new_layout_for(&s, Repr::Uniform); + let packed = TypeLayout::new_layout_for(&s, Repr::Packed); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(4)); + assert_eq!(uniform.byte_size(), Some(16)); + assert_eq!(packed.byte_size(), Some(4)); + } + + #[test] + fn test_nested_struct_field_offset() { + let s = SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1)); + let s = SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1)) + .extend("b", s) // offset 4 for storage and packed, offset 16 for uniform + .into(); + + let storage = TypeLayout::new_layout_for(&s, Repr::Storage); + let uniform = TypeLayout::new_layout_for(&s, Repr::Uniform); + let packed = TypeLayout::new_layout_for(&s, Repr::Packed); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(8)); + // field b is bytes 16..=19 and struct size must be a multiple of the struct align (16) + assert_eq!(uniform.byte_size(), Some(32)); + assert_eq!(packed.byte_size(), Some(8)); + + match (storage.kind, uniform.kind, packed.kind) { + ( + TypeLayoutSemantics::Structure(storage), + TypeLayoutSemantics::Structure(uniform), + TypeLayoutSemantics::Structure(packed), + ) => { + assert_eq!(storage.fields[1].rel_byte_offset, 4); + assert_eq!(uniform.fields[1].rel_byte_offset, 16); + assert_eq!(packed.fields[1].rel_byte_offset, 4); + } + _ => panic!("Unexpected layout kind"), + } + } + + #[test] + fn test_array_in_struct_field_offset() { + let s = SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1)) + .extend( + "b", + SizedArray::new( + Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), + NonZeroU32::new(1).unwrap(), + ), + ) // offset 4 for storage and packed, offset 16 for uniform + .into(); + + let storage = TypeLayout::new_layout_for(&s, Repr::Storage); + let uniform = TypeLayout::new_layout_for(&s, Repr::Uniform); + let packed = TypeLayout::new_layout_for(&s, Repr::Packed); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(8)); + // field b is bytes 16..=19 and struct size must be a multiple of the struct align (16) + assert_eq!(uniform.byte_size(), Some(32)); + assert_eq!(packed.byte_size(), Some(8)); + + match (storage.kind, uniform.kind, packed.kind) { + ( + TypeLayoutSemantics::Structure(storage), + TypeLayoutSemantics::Structure(uniform), + TypeLayoutSemantics::Structure(packed), + ) => { + assert_eq!(storage.fields[1].rel_byte_offset, 4); + assert_eq!(uniform.fields[1].rel_byte_offset, 16); + assert_eq!(packed.fields[1].rel_byte_offset, 4); + } + _ => panic!("Unexpected layout kind"), + } + } + + #[test] + fn test_unsized_struct_layout() { + let unsized_struct = UnsizedStruct { + name: CanonName::from("TestStruct"), + sized_fields: vec![ + SizedField { + name: CanonName::from("field1"), + custom_min_size: None, + custom_min_align: None, + ty: Vector::new(ScalarType::F32, Len::X2).into(), + }, + SizedField { + name: CanonName::from("field2"), + custom_min_size: None, + custom_min_align: None, + ty: Vector::new(ScalarType::F32, Len::X1).into(), + }, + ], + last_unsized: RuntimeSizedArrayField { + name: CanonName::from("dynamic_array"), + custom_min_align: None, + array: RuntimeSizedArray { + element: Vector::new(ScalarType::F32, Len::X1).into(), + }, + }, + } + .into(); + + let layout = TypeLayout::new_layout_for(&unsized_struct, Repr::Storage); + assert_eq!(layout.byte_size(), None); + assert!(layout.align().as_u64() == 8); // align of vec2 + match &layout.kind { + TypeLayoutSemantics::Structure(struct_layout) => { + assert_eq!(struct_layout.fields.len(), 3); + assert_eq!(struct_layout.fields[0].field.name, CanonName::from("field1")); + assert_eq!(struct_layout.fields[1].field.name, CanonName::from("field2")); + assert_eq!(struct_layout.fields[2].field.name, CanonName::from("dynamic_array")); + + assert_eq!(struct_layout.fields[0].rel_byte_offset, 0); // vec2 + assert_eq!(struct_layout.fields[1].rel_byte_offset, 8); // f32 + assert_eq!(struct_layout.fields[2].rel_byte_offset, 12); // Array + // The last field should be an unsized array + match &struct_layout.fields[2].field.ty.kind { + TypeLayoutSemantics::Array(array, None) => assert_eq!(array.byte_stride, 4), + _ => panic!("Expected runtime-sized array for last field"), + } + } + _ => panic!("Expected structure layout"), + } + + // Testing uniform representation + let layout = TypeLayout::new_layout_for(&unsized_struct, Repr::Uniform); + assert_eq!(layout.byte_size(), None); + // Struct alignmment has to be a multiple of 16, but the runtime sized array + // also has an alignment of 16, which transfers to the struct alignment. + assert!(layout.align().as_u64() == 16); + match &layout.kind { + TypeLayoutSemantics::Structure(struct_layout) => { + assert_eq!(struct_layout.fields[0].rel_byte_offset, 0); // vec2 + assert_eq!(struct_layout.fields[1].rel_byte_offset, 8); // f32 + // array has alignment of 16, so offset should be 16 + assert_eq!(struct_layout.fields[2].rel_byte_offset, 16); // Array + match &struct_layout.fields[2].ty.kind { + // Stride has to be a multiple of 16 in uniform address space + TypeLayoutSemantics::Array(array, None) => assert_eq!(array.byte_stride, 16), + _ => panic!("Expected runtime-sized array for last field"), + } + } + _ => panic!("Expected structure layout"), + } + } +} From 5372cd58695d526fec299b7aee1289ba47a646aa Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 8 Jul 2025 15:11:36 +0200 Subject: [PATCH 132/182] cleanup align size tests --- .../type_layout/layoutable/align_size.rs | 70 +++++-------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 11f4d11..a6b431b 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -124,9 +124,7 @@ impl<'a> FieldOffsets<'a> { pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); impl Iterator for FieldOffsetsSized<'_> { type Item = u64; - fn next(&mut self) -> Option { - self.0.next() - } + fn next(&mut self) -> Option { self.0.next() } } impl<'a> FieldOffsetsSized<'a> { /// Consumes self and calculates the byte size and align of a struct @@ -139,9 +137,7 @@ impl<'a> FieldOffsetsSized<'a> { } /// Returns the inner iterator over sized fields. - pub fn into_inner(self) -> FieldOffsets<'a> { - self.0 - } + pub fn into_inner(self) -> FieldOffsets<'a> { self.0 } } /// The field offsets of an `UnsizedStruct`. @@ -163,9 +159,7 @@ impl<'a> FieldOffsetsUnsized<'a> { } /// Returns an iterator over the sized field offsets. - pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { - &mut self.sized - } + pub fn sized_field_offsets(&mut self) -> &mut FieldOffsets<'a> { &mut self.sized } /// Returns the last field's offset and the struct's align. pub fn last_field_offset_and_struct_align(mut self) -> (u64, U32PowerOf2) { @@ -178,9 +172,7 @@ impl<'a> FieldOffsetsUnsized<'a> { } /// Returns the inner iterator over sized fields. - pub fn into_inner(self) -> FieldOffsets<'a> { - self.sized - } + pub fn into_inner(self) -> FieldOffsets<'a> { self.sized } } impl UnsizedStruct { @@ -194,16 +186,12 @@ impl UnsizedStruct { } /// This is expensive as it calculates the byte align by traversing all fields recursively. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - self.field_offsets(repr).last_field_offset_and_struct_align().1 - } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } } #[allow(missing_docs)] impl Vector { - pub const fn new(scalar: ScalarType, len: Len) -> Self { - Self { scalar, len } - } + pub const fn new(scalar: ScalarType, len: Len) -> Self { Self { scalar, len } } pub const fn byte_size(&self, repr: Repr) -> u64 { match repr { @@ -290,9 +278,7 @@ impl Matrix { #[allow(missing_docs)] impl Atomic { - pub const fn byte_size(&self) -> u64 { - self.scalar.as_scalar_type().byte_size() - } + pub const fn byte_size(&self) -> u64 { self.scalar.as_scalar_type().byte_size() } pub const fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { Repr::Packed => return PACKED_ALIGN, @@ -305,13 +291,9 @@ impl Atomic { #[allow(missing_docs)] impl SizedArray { - pub fn byte_size(&self, repr: Repr) -> u64 { - array_size(self.byte_stride(repr), self.len) - } + pub fn byte_size(&self, repr: Repr) -> u64 { array_size(self.byte_stride(repr), self.len) } - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - array_align(self.element.align(repr), repr) - } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } pub fn byte_stride(&self, repr: Repr) -> u64 { let (element_size, element_align) = self.element.byte_size_and_align(repr); @@ -322,9 +304,7 @@ impl SizedArray { /// Returns an array's size given it's stride and length. /// /// Note, this is independent of layout rules (`Repr`). -pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { - array_stride * len.get() as u64 -} +pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_stride * len.get() as u64 } /// Returns an array's size given the alignment of it's elements. pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { @@ -354,13 +334,9 @@ pub const fn array_stride(element_align: U32PowerOf2, element_size: u64, repr: R #[allow(missing_docs)] impl RuntimeSizedArray { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { - array_align(self.element.align(repr), repr) - } + pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } - pub fn byte_stride(&self, repr: Repr) -> u64 { - array_stride(self.align(repr), self.element.byte_size(repr), repr) - } + pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr), repr) } } #[allow(missing_docs)] @@ -493,14 +469,10 @@ impl LayoutCalculator { // where justPastLastMember = OffsetOfMember(S,N) + SizeOfMember(S,N) // // self.next_offset_min is justPastLastMember already. - pub const fn byte_size(&self) -> u64 { - round_up(self.align().as_u64(), self.next_offset_min) - } + pub const fn byte_size(&self) -> u64 { round_up(self.align().as_u64(), self.next_offset_min) } /// Returns the align of the struct. - pub const fn align(&self) -> U32PowerOf2 { - Self::adjust_struct_alignment_for_repr(self.align, self.repr) - } + pub const fn align(&self) -> U32PowerOf2 { Self::adjust_struct_alignment_for_repr(self.align, self.repr) } const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { let field_align = Self::calculate_align(field_align, field_custom_min_align); @@ -848,12 +820,10 @@ mod tests { #[test] fn test_array_align() { let element_align = U32PowerOf2::_8; - assert_eq!(array_align(element_align, Repr::Storage), U32PowerOf2::_8); assert_eq!(array_align(element_align, Repr::Uniform), U32PowerOf2::_16); assert_eq!(array_align(element_align, Repr::Packed), U32PowerOf2::_1); - // Test with smaller alignment let small_align = U32PowerOf2::_4; assert_eq!(array_align(small_align, Repr::Storage), U32PowerOf2::_4); assert_eq!(array_align(small_align, Repr::Uniform), U32PowerOf2::_16); @@ -898,7 +868,6 @@ mod tests { // Add a u32 field - should be packed without padding let offset1 = calc.extend(4, U32PowerOf2::_4, None, None, false); assert_eq!(offset1, 0); - // Add a vec2 field - should be packed directly after let offset2 = calc.extend(8, U32PowerOf2::_8, None, None, false); assert_eq!(offset2, 4); @@ -906,7 +875,7 @@ mod tests { assert_eq!(calc.byte_size(), 12); assert_eq!(calc.align(), U32PowerOf2::_1); - // Add a vec2 field - but with custom min align, which overwrites paacked alignment + // Add a vec2 field - but with custom min align, which overwrites packed alignment let offset3 = calc.extend(8, U32PowerOf2::_8, None, Some(U32PowerOf2::_16), false); assert_eq!(offset3, 16); // TODO(chronicl) not sure whether the alignment should stay 1 for a packesd struct @@ -937,7 +906,6 @@ mod tests { let offset1 = calc.extend(4, U32PowerOf2::_4, Some(33), None, false); assert_eq!(offset1, 0); assert_eq!(calc.byte_size(), 36); // 33 rounded up to multiple of align - // Add field with custom minimum alignment let offset2 = calc.extend(4, U32PowerOf2::_4, None, Some(U32PowerOf2::_16), false); assert_eq!(offset2, 48); @@ -954,7 +922,6 @@ mod tests { // Add some sized fields first calc.extend(4, U32PowerOf2::_4, None, None, false); calc.extend(8, U32PowerOf2::_8, None, None, false); - // Add unsized field let (offset, align) = calc.extend_unsized(U32PowerOf2::_4, None); assert_eq!(offset, 16); @@ -963,11 +930,11 @@ mod tests { #[test] fn test_sized_field_calculations() { + // Test custom size let field = SizedField::new( FieldOptions::new("test_field", None, Some(16)), SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), ); - // Vector is 8 bytes, but field has custom min size of 16 assert_eq!(field.byte_size(Repr::Storage), 16); assert_eq!(field.align(Repr::Storage), U32PowerOf2::_8); @@ -977,7 +944,6 @@ mod tests { FieldOptions::new("test_field2", Some(U32PowerOf2::_16), None), SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), ); - assert_eq!(field2.align(Repr::Storage), U32PowerOf2::_16); } @@ -996,7 +962,7 @@ mod tests { } #[test] - fn test_complex_struct_layout() { + fn test_sized_struct_layout() { // Create a struct with mixed field types let sized_struct = SizedStruct::new( "TestStruct", @@ -1040,7 +1006,7 @@ mod tests { let (size, align) = sized_struct.byte_size_and_align(Repr::Uniform); - assert_eq!(align, U32PowerOf2::_16); // Alignment adjusted for uniform + assert_eq!(align, U32PowerOf2::_16); // Alignment adjusted for uniform to multiple of 16 assert_eq!(size, 16); // Byte size of struct is a multiple of it's alignment } From 8f561e0488d6592a34138aa754836e7ebad79844 Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 8 Jul 2025 15:17:54 +0200 Subject: [PATCH 133/182] Make clippy happy --- .../rust_types/type_layout/construction.rs | 17 +++++------------ .../type_layout/layoutable/align_size.rs | 2 +- .../type_layout/layoutable/ir_compat.rs | 2 +- shame/src/ir/pipeline/wip_pipeline.rs | 1 + 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index 4829021..ec590e6 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -244,9 +244,7 @@ fn check_compare_sized(ctx: LayoutContext, ty: &SizedType) -> Result<(), LayoutE check_sized_fields( ctx, s, - s.fields() - .into_iter() - .zip((&mut actual_offsets).zip(&mut expected_offsets)), + s.fields().iter().zip((&mut actual_offsets).zip(&mut expected_offsets)), ) } SizedType::Array(a) => { @@ -338,9 +336,7 @@ impl From> for LayoutErrorContext { } impl Repr { - fn more_info_at(&self) -> &str { - "https://www.w3.org/TR/WGSL/#memory-layouts" - } + fn more_info_at(&self) -> &str { "https://www.w3.org/TR/WGSL/#memory-layouts" } } #[allow(missing_docs)] @@ -397,14 +393,10 @@ pub enum StructKind { } impl From for StructKind { - fn from(value: SizedStruct) -> Self { - StructKind::Sized(value) - } + fn from(value: SizedStruct) -> Self { StructKind::Sized(value) } } impl From for StructKind { - fn from(value: UnsizedStruct) -> Self { - StructKind::Unsized(value) - } + fn from(value: UnsizedStruct) -> Self { StructKind::Unsized(value) } } impl std::error::Error for StructFieldOffsetError {} @@ -487,6 +479,7 @@ impl Display for StructFieldOffsetError { "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", expected_alignment, self.field_name )?; + #[allow(clippy::single_match)] match (self.ctx.actual_repr, self.ctx.expected_repr) { (Repr::Storage, Repr::Uniform) => writeln!( f, diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index a6b431b..d5919c6 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -228,7 +228,7 @@ impl ScalarType { pub const fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { - Repr::Packed => return PACKED_ALIGN, + Repr::Packed => PACKED_ALIGN, Repr::Storage | Repr::Uniform => match self { ScalarType::F16 => U32PowerOf2::_2, ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index bcb8512..66ba67d 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -134,7 +134,7 @@ impl TryFrom for ir::ir_type::SizedStruct { fn try_from(ty: SizedStruct) -> Result { let (last_field, first_fields) = ty.fields_split_last(); - let first_fields: Result, _> = first_fields.into_iter().map(|f| f.clone().try_into()).collect(); + let first_fields: Result, _> = first_fields.iter().map(|f| f.clone().try_into()).collect(); let last_field_ir = last_field.clone().try_into()?; match ir::ir_type::SizedStruct::new_nonempty(ty.name.clone(), first_fields?, last_field_ir) { diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index d55df5f..4a245d2 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -416,6 +416,7 @@ impl WipPushConstantsField { // here we have to allocate unique name strings for each field, // so we don't fail the name uniqueness check, even though we don't need those names. + #[allow(clippy::match_single_binding)] SizedStruct::new_nonempty("PushConstants".into(), fields.iter().map(&mut to_sized_field).collect(), to_sized_field(last) From 255fb80d045533523bc1a11fef828761b7d6cc1d Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 11 Jul 2025 10:35:44 +0200 Subject: [PATCH 134/182] Rename LayoutCalculator -> StructLayoutCalculator --- .../type_layout/layoutable/align_size.rs | 24 +++++++++---------- .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 4 ++-- shame/src/lib.rs | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index d5919c6..1b58c0d 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -91,7 +91,7 @@ impl SizedStruct { pub struct FieldOffsets<'a> { fields: &'a [SizedField], field_index: usize, - calc: LayoutCalculator, + calc: StructLayoutCalculator, repr: Repr, } impl Iterator for FieldOffsets<'_> { @@ -113,7 +113,7 @@ impl<'a> FieldOffsets<'a> { Self { fields, field_index: 0, - calc: LayoutCalculator::new(repr), + calc: StructLayoutCalculator::new(repr), repr, } } @@ -342,12 +342,12 @@ impl RuntimeSizedArray { #[allow(missing_docs)] impl SizedField { pub fn byte_size(&self, repr: Repr) -> u64 { - LayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) + StructLayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) } pub fn align(&self, repr: Repr) -> U32PowerOf2 { // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. // This is intended! - LayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) + StructLayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) } } @@ -356,7 +356,7 @@ impl RuntimeSizedArrayField { pub fn align(&self, repr: Repr) -> U32PowerOf2 { // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. // This is intended! - LayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) + StructLayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) } } @@ -385,13 +385,13 @@ pub const fn round_up_align(multiple_of: U32PowerOf2, n: U32PowerOf2) -> U32Powe /// are ignored and the field is inserted directly after the previous field. However, /// a `custom_min_align` that is `Some` overwrites the "packedness" of the field. #[derive(Debug, Clone)] -pub struct LayoutCalculator { +pub struct StructLayoutCalculator { next_offset_min: u64, align: U32PowerOf2, repr: Repr, } -impl LayoutCalculator { +impl StructLayoutCalculator { /// Creates a new `LayoutCalculator`, which calculates the size, align and /// the field offsets of a gpu struct. pub const fn new(repr: Repr) -> Self { @@ -845,7 +845,7 @@ mod tests { #[test] fn test_layout_calculator_basic() { - let mut calc = LayoutCalculator::new(Repr::Storage); + let mut calc = StructLayoutCalculator::new(Repr::Storage); // Add a u32 field let offset1 = calc.extend(4, U32PowerOf2::_4, None, None, false); @@ -863,7 +863,7 @@ mod tests { #[test] fn test_layout_calculator_packed() { - let mut calc = LayoutCalculator::new(Repr::Packed); + let mut calc = StructLayoutCalculator::new(Repr::Packed); // Add a u32 field - should be packed without padding let offset1 = calc.extend(4, U32PowerOf2::_4, None, None, false); @@ -885,7 +885,7 @@ mod tests { #[test] fn test_layout_calculator_uniform_struct_padding() { - let mut calc = LayoutCalculator::new(Repr::Uniform); + let mut calc = StructLayoutCalculator::new(Repr::Uniform); // Add a nested struct with size 12 let offset1 = calc.extend(12, U32PowerOf2::_4, None, None, true); @@ -900,7 +900,7 @@ mod tests { #[test] fn test_layout_calculator_custom_sizes_and_aligns() { - let mut calc = LayoutCalculator::new(Repr::Storage); + let mut calc = StructLayoutCalculator::new(Repr::Storage); // Add field with custom minimum size let offset1 = calc.extend(4, U32PowerOf2::_4, Some(33), None, false); @@ -917,7 +917,7 @@ mod tests { #[test] fn test_layout_calculator_extend_unsized() { - let mut calc = LayoutCalculator::new(Repr::Storage); + let mut calc = StructLayoutCalculator::new(Repr::Storage); // Add some sized fields first calc.extend(4, U32PowerOf2::_4, None, None, false); diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 5965c48..73de0e7 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -17,7 +17,7 @@ pub(crate) mod align_size; pub(crate) mod builder; pub(crate) mod ir_compat; -pub use align_size::{FieldOffsets, MatrixMajor, LayoutCalculator, array_size, array_stride, array_align}; +pub use align_size::{FieldOffsets, MatrixMajor, StructLayoutCalculator, array_size, array_stride, array_align}; pub use builder::{SizedOrArray, FieldOptions}; /// Types that can be layed out in memory. diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index b88d9d4..69bca48 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -19,7 +19,7 @@ use crate::{ }, }; use layoutable::{ - align_size::{LayoutCalculator, PACKED_ALIGN}, + align_size::{StructLayoutCalculator, PACKED_ALIGN}, LayoutableType, Matrix, Vector, }; @@ -171,7 +171,7 @@ pub mod repr { impl DerivableRepr for Packed {} /// Enum of layout rules. - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Repr { /// Wgsl storage address space layout /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints diff --git a/shame/src/lib.rs b/shame/src/lib.rs index c9d39d1..144ed18 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -512,7 +512,7 @@ pub mod any { pub use type_layout::layoutable::FieldOptions; // layout calculation utility - pub use type_layout::layoutable::LayoutCalculator; + pub use type_layout::layoutable::StructLayoutCalculator; pub use type_layout::layoutable::FieldOffsets; // conversion and builder errors From defb6fa98e8877b2c23667c12a54ca5f378f6fce Mon Sep 17 00:00:00 2001 From: chronicl Date: Fri, 11 Jul 2025 11:16:38 +0200 Subject: [PATCH 135/182] Make Repr::Packed ignore custom_min_align attributes and catch simultaneous usage in GpuLayout derive --- .../type_layout/layoutable/align_size.rs | 61 +++++++++++++++---- shame/tests/test_layout.rs | 61 ++++++++++--------- shame_derive/src/derive_layout.rs | 20 ++++++ 3 files changed, 101 insertions(+), 41 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 1b58c0d..642cbe6 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -347,7 +347,7 @@ impl SizedField { pub fn align(&self, repr: Repr) -> U32PowerOf2 { // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. // This is intended! - StructLayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align) + StructLayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align, repr) } } @@ -356,7 +356,7 @@ impl RuntimeSizedArrayField { pub fn align(&self, repr: Repr) -> U32PowerOf2 { // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. // This is intended! - StructLayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align) + StructLayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align, repr) } } @@ -422,7 +422,7 @@ impl StructLayoutCalculator { } let size = Self::calculate_byte_size(field_size, custom_min_size); - let align = Self::calculate_align(field_align, custom_min_align); + let align = Self::calculate_align(field_align, custom_min_align, self.repr); let offset = self.next_field_offset(align, custom_min_align); self.next_offset_min = match (self.repr, is_struct) { @@ -455,7 +455,7 @@ impl StructLayoutCalculator { Repr::Storage | Repr::Uniform => {} } - let align = Self::calculate_align(field_align, custom_min_align); + let align = Self::calculate_align(field_align, custom_min_align, self.repr); let offset = self.next_field_offset(align, custom_min_align); self.align = self.align.max(align); @@ -475,7 +475,7 @@ impl StructLayoutCalculator { pub const fn align(&self) -> U32PowerOf2 { Self::adjust_struct_alignment_for_repr(self.align, self.repr) } const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { - let field_align = Self::calculate_align(field_align, field_custom_min_align); + let field_align = Self::calculate_align(field_align, field_custom_min_align, self.repr); match (self.repr, field_custom_min_align) { (Repr::Packed, None) => self.next_offset_min, (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), @@ -493,12 +493,22 @@ impl StructLayoutCalculator { byte_size } - pub(crate) const fn calculate_align(align: U32PowerOf2, custom_min_align: Option) -> U32PowerOf2 { - // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) - if let Some(min_align) = custom_min_align { - align.max(min_align) - } else { - align + pub(crate) const fn calculate_align( + align: U32PowerOf2, + custom_min_align: Option, + repr: Repr, + ) -> U32PowerOf2 { + match repr { + Repr::Storage | Repr::Uniform => { + // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) + if let Some(min_align) = custom_min_align { + align.max(min_align) + } else { + align + } + } + // custom_min_align is ignored in packed structs and the align is always 1. + Repr::Packed => PACKED_ALIGN, } } @@ -957,8 +967,8 @@ mod tests { // Array has 8-byte alignment, but field has custom min align of 16 assert_eq!(field.align(Repr::Storage), U32PowerOf2::_16); - // Test packed representation with custom alignment - assert_eq!(field.align(Repr::Packed), U32PowerOf2::_16); + // Custom min align is ignored by packed + assert_eq!(field.align(Repr::Packed), U32PowerOf2::_1); } #[test] @@ -1071,4 +1081,29 @@ mod tests { assert_eq!(size, 16); assert_eq!(align, U32PowerOf2::_1); } + + #[test] + fn test_packed_ignores_custom_min_align() { + let mut calc = StructLayoutCalculator::new(Repr::Packed); + + // Add a u32 field with custom min align of 16 + let offset1 = calc.extend(4, U32PowerOf2::_4, None, Some(U32PowerOf2::_16), false); + assert_eq!(offset1, 0); + // Add a vec2 field - should be packed directly after + let offset2 = calc.extend(8, U32PowerOf2::_8, None, None, false); + assert_eq!(offset2, 4); + + assert_eq!(calc.byte_size(), 12); + assert_eq!(calc.align(), U32PowerOf2::_1); // Packed structs always have align of 1 + + let s = SizedStruct::new( + "TestStruct", + FieldOptions::new("field1", Some(U32PowerOf2::_16), None), + SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 8 bytes, 8-byte aligned + ); + + // The custom min align is ignored in packed structs + assert_eq!(s.byte_size_and_align(Repr::Packed).1, U32PowerOf2::_1); + assert_eq!(s.fields()[0].align(Repr::Packed), U32PowerOf2::_1); + } } diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index a494cba..deb49bb 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -384,15 +384,6 @@ fn external_vec_type() { c: f32x4, } - #[derive(sm::GpuLayout)] - #[gpu_repr(packed)] - struct OnGpu2Packed { - a: f32x3, - b: f32x3, - #[align(16)] - c: f32x4, - } - #[derive(sm::CpuLayout)] #[repr(C)] struct OnCpu2 { @@ -402,7 +393,19 @@ fn external_vec_type() { } assert_ne!(gpu_layout::(), cpu_layout::()); - assert_eq!(gpu_layout::(), cpu_layout::()); + + // TODO: delete or use compile fail test crate like trybuild to make + // sure that align and size attributes aren't allowed on packed structs. + // #[derive(sm::GpuLayout)] + // #[gpu_repr(packed)] + // struct OnGpu2Packed { + // a: f32x3, + // b: f32x3, + // #[align(16)] + // c: f32x4, + // } + + // assert_eq!(gpu_layout::(), cpu_layout::()); } #[test] @@ -427,23 +430,25 @@ fn external_vec_type() { assert_eq!(gpu_layout::(), cpu_layout::()); } { - #[derive(sm::GpuLayout)] - #[gpu_repr(packed)] - struct OnGpu { - pos: f32x3, - nor: f32x3, - #[align(8)] uv : f32x2, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - pos: f32x3_align4, - nor: f32x3_align4, - uv : f32x2_cpu, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); - enum __ where OnGpu: sm::VertexLayout {} + // TODO: delete or use compile fail test crate like trybuild to make + // sure that align and size attributes aren't allowed on packed structs. + // #[derive(sm::GpuLayout)] + // #[gpu_repr(packed)] + // struct OnGpu { + // pos: f32x3, + // nor: f32x3, + // #[align(8)] uv : f32x2, + // } + + // #[derive(sm::CpuLayout)] + // #[repr(C)] + // struct OnCpu { + // pos: f32x3_align4, + // nor: f32x3_align4, + // uv : f32x2_cpu, + // } + + // assert_eq!(gpu_layout::(), cpu_layout::()); + // enum __ where OnGpu: sm::VertexLayout {} } } diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index cc0a7e9..c6c12c9 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -156,6 +156,26 @@ pub fn impl_for_struct( align: util::find_literal_list_attr::("align", &field.attrs)?, }; + match gpu_repr { + Repr::Packed => { + if fwa.align.is_some() { + bail!( + field.span(), + "`#[gpu_repr(packed)]` structs do not support `#[align(N)]` attributes" + ); + } + // TODO(chronicl) decide on whether size attribute is allowed. Will have to be adjusted in + // LayoutCalculator too! + // if fwa.size.is_some() { + // bail!( + // field.span(), + // "`#[gpu_repr(packed)]` structs do not support `#[size(N)]` attributes" + // ); + // } + } + Repr::Storage => {} + } + if let Some((span, align_lit)) = &fwa.align { match align_lit.base10_parse().map(u32::is_power_of_two) { Ok(true) => (), From 737522c160ac203cc3a1213077ae85552ba2ab71 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 19 Jul 2025 16:41:09 +0200 Subject: [PATCH 136/182] . --- shame/src/common/proc_macro_reexports.rs | 1 - shame/src/frontend/any/render_io.rs | 9 +- shame/src/frontend/encoding/buffer.rs | 3 +- shame/src/frontend/encoding/io_iter.rs | 6 +- shame/src/frontend/rust_types/array.rs | 35 +- shame/src/frontend/rust_types/atomic.rs | 10 +- .../src/frontend/rust_types/layout_traits.rs | 80 ++-- shame/src/frontend/rust_types/mat.rs | 10 +- shame/src/frontend/rust_types/packed_vec.rs | 20 +- shame/src/frontend/rust_types/struct_.rs | 7 +- .../rust_types/type_layout/compatible_with.rs | 447 ++++++++++++++++++ .../rust_types/type_layout/construction.rs | 116 ----- .../src/frontend/rust_types/type_layout/eq.rs | 8 +- .../type_layout/layoutable/align_size.rs | 169 ++++--- .../type_layout/layoutable/builder.rs | 18 +- .../type_layout/layoutable/ir_compat.rs | 19 +- .../rust_types/type_layout/layoutable/mod.rs | 27 +- .../type_layout/layoutable/to_layout.rs | 177 +++++++ .../frontend/rust_types/type_layout/mod.rs | 183 ++++++- shame/src/frontend/rust_types/vec.rs | 14 +- shame/src/ir/pipeline/wip_pipeline.rs | 4 +- shame/src/lib.rs | 3 - shame_derive/src/derive_layout.rs | 28 +- 23 files changed, 1038 insertions(+), 356 deletions(-) create mode 100644 shame/src/frontend/rust_types/type_layout/compatible_with.rs create mode 100644 shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 4bbb888..f69df9a 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -42,7 +42,6 @@ pub use crate::frontend::rust_types::type_traits::NoHandles; pub use crate::frontend::rust_types::type_traits::VertexAttribute; pub use crate::frontend::rust_types::type_traits::GpuLayoutField; pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; -pub use crate::frontend::rust_types::type_layout::layoutable::Layoutable; pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index eb72193..4ce732a 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,7 +2,7 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; -use crate::any::layout::{GpuTypeLayout, TypeRepr}; +use crate::any::layout::{GpuTypeLayout, Repr, TypeRepr}; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::{layoutable, TypeLayout}; use crate::{ @@ -246,14 +246,13 @@ impl VertexAttribFormat { } impl Attrib { - pub(crate) fn get_attribs_and_stride( - gpu_layout: &GpuTypeLayout, + pub(crate) fn get_attribs_and_stride( + layout: &TypeLayout, mut location_counter: &LocationCounter, ) -> Option<(Box<[Attrib]>, u64)> { - let layout = gpu_layout.layout(); let stride = { let size = layout.byte_size()?; - layoutable::array_stride(layout.align(), size, T::REPR) + layoutable::array_stride(layout.align(), size, Repr::Storage) }; use TypeLayoutSemantics as TLS; diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index d0cf58e..c4a7dab 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -1,4 +1,3 @@ -use crate::any::layout::Layoutable; use crate::common::proc_macro_reexports::GpuStoreImplCategory; use crate::frontend::any::shared_io::{BindPath, BindingType, BufferBindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -319,7 +318,7 @@ fn store_type_from_impl_category(category: GpuStoreImplCategory) -> ir::StoreTyp #[rustfmt::skip] impl Binding for Buffer, AS, DYN_OFFSET> where - T: GpuType + GpuSized + GpuLayout + Layoutable + T: GpuType + GpuSized + GpuLayout { fn binding_type() -> BindingType { BufferInner::::binding_type(DYN_OFFSET) } #[track_caller] diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index 7b6c2a3..c53513a 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -107,10 +107,10 @@ impl VertexBuffer<'_, T> { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); + let layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); - let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { - ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout.layout()).into()); + let attribs_and_stride = Attrib::get_attribs_and_stride(&layout, &location_counter).ok_or_else(|| { + ctx.push_error(FrontendError::MalformedVertexBufferLayout(layout).into()); InvalidReason::ErrorThatWasPushed }); diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 9ae117e..e5101a3 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -12,7 +12,6 @@ use super::type_traits::{ use super::vec::{ToInteger, ToVec}; use super::{AsAny, GpuType}; use super::{To, ToGpuType}; -use crate::any::layout::{Layoutable}; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::Any; @@ -21,6 +20,7 @@ use crate::frontend::encoding::buffer::{Buffer, BufferAddressSpace, BufferInner, use crate::frontend::encoding::flow::{for_range_impl, FlowFn}; use crate::frontend::error::InternalError; use crate::frontend::rust_types::reference::Ref; +use crate::frontend::rust_types::type_layout::ArrayLayout; use crate::frontend::rust_types::vec::vec; use crate::ir::ir_type::stride_of_array_from_element_align_size; use crate::ir::pipeline::StageMask; @@ -156,14 +156,6 @@ impl GpuSized for Array> { #[rustfmt::skip] impl NoHandles for Array {} #[rustfmt::skip] impl NoAtomics for Array {} #[rustfmt::skip] impl NoBools for Array {} -impl Layoutable for Array { - fn layoutable_type() -> layoutable::LayoutableType { - match N::LEN { - Some(n) => layoutable::SizedArray::new(Rc::new(T::layoutable_type_sized()), n).into(), - None => layoutable::RuntimeSizedArray::new(T::layoutable_type_sized()).into(), - } - } -} impl ToGpuType for Array { type Gpu = Self; @@ -173,8 +165,13 @@ impl ToGpuType for Array { fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { Some(self) } } -impl GpuLayout for Array { - type GpuRepr = repr::Storage; +impl GpuLayout for Array { + fn layout_recipe() -> layoutable::LayoutableType { + match N::LEN { + Some(n) => layoutable::SizedArray::new(Rc::new(T::layout_recipe_sized()), n).into(), + None => layoutable::RuntimeSizedArray::new(T::layout_recipe_sized()).into(), + } + } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let (t_cpu_name, t_cpu_layout) = match T::cpu_type_name_and_layout()? { @@ -195,15 +192,13 @@ impl GpuLayout for TypeLayout::new( N::LEN.map(|n| n.get() as u64 * t_cpu_size), t_cpu_layout.align(), - TypeLayoutSemantics::Array( - Rc::new(ElementLayout { - // array stride is element size according to - // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.size - byte_stride: t_cpu_size, - ty: t_cpu_layout, - }), - N::LEN.map(NonZeroU32::get), - ), + TypeLayoutSemantics::Array(Rc::new(ArrayLayout { + // array stride is element size according to + // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.size + byte_stride: t_cpu_size, + element_ty: t_cpu_layout, + len: N::LEN.map(NonZeroU32::get), + })), ), ); diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index aec5ca4..79a5d89 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -18,7 +18,7 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref}; +use crate::{frontend::rust_types::reference::Ref}; use crate::{ boolx1, frontend::{ @@ -133,17 +133,13 @@ impl GetAllFields for Atomic { fn fields_as_anys_unchecked(self_as_any: Any) -> impl std::borrow::Borrow<[Any]> { [] } } -impl Layoutable for Atomic { - fn layoutable_type() -> layoutable::LayoutableType { +impl GpuLayout for Atomic { + fn layout_recipe() -> layoutable::LayoutableType { layoutable::Atomic { scalar: T::SCALAR_TYPE_INTEGER, } .into() } -} - -impl GpuLayout for Atomic { - type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index f04e155..3588e57 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,4 +1,4 @@ -use crate::any::layout::{Layoutable, Repr}; +use crate::any::layout::{LayoutableType, Repr, SizedType}; use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; @@ -10,6 +10,7 @@ use crate::frontend::encoding::buffer::{BufferAddressSpace, BufferInner, BufferR use crate::frontend::encoding::{EncodingError, EncodingErrorKind}; use crate::frontend::error::InternalError; use crate::frontend::rust_types::len::*; +use crate::frontend::rust_types::type_layout::ArrayLayout; use crate::ir::ir_type::{ align_of_array, align_of_array_from_element_alignment, byte_size_of_array_from_stride_len, round_up, stride_of_array_from_element_align_size, CanonName, LayoutError, ScalarTypeFp, ScalarTypeInteger, @@ -26,7 +27,7 @@ use super::type_layout::repr::{TypeRepr, DerivableRepr}; use super::type_layout::layoutable::{self, array_stride, Vector}; use super::type_layout::{ self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, GpuTypeLayout, StructLayout, TypeLayout, - TypeLayoutSemantics, + TypeLayoutSemantics, DEFAULT_REPR, }; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -35,7 +36,7 @@ use super::{len::Len, scalar_type::ScalarType, vec::vec}; use super::{AsAny, GpuType, ToGpuType}; use crate::frontend::any::{shared_io::BindPath, shared_io::BindingType}; use crate::frontend::rust_types::reference::Ref; -use crate::ir::{self, AlignedType, ScalarType as ST, SizedStruct, SizedType, StoreType}; +use crate::ir::{self, AlignedType, ScalarType as ST, SizedStruct, StoreType}; use std::borrow::{Borrow, Cow}; use std::iter::Empty; use std::mem::size_of; @@ -138,12 +139,21 @@ use std::rc::Rc; /// [`Texture`]: crate::Texture /// [`StorageTexture`]: crate::StorageTexture /// -pub trait GpuLayout: Layoutable { - /// Returns the `Repr` of the `TypeLayout` from `GpuLayout::gpu_layout`. - /// - /// `GpuRepr` only exists so a (derived) struct can be packed for use in vertex buffer usage. - /// It is `repr::Storage` in all other cases. - type GpuRepr: DerivableRepr; +pub trait GpuLayout { + /// TODO(chronicl) + fn layout_recipe() -> LayoutableType; + /// TODO(chronicl) + fn layout_recipe_sized() -> SizedType + where + Self: GpuSized, + { + match Self::layout_recipe() { + LayoutableType::Sized(s) => s, + LayoutableType::RuntimeSizedArray(_) | LayoutableType::UnsizedStruct(_) => { + unreachable!("Self is GpuSized, which these LayoutableType variants aren't.") + } + } + } /// the `#[cpu(...)]` in `#[derive(GpuLayout)]` allows the definition of a /// corresponding Cpu type to the Gpu type that the derive macro is used on. @@ -178,11 +188,7 @@ pub trait GpuLayout: Layoutable { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` -pub fn gpu_layout() -> TypeLayout { gpu_type_layout::().layout() } - -pub fn gpu_type_layout() -> GpuTypeLayout { - GpuTypeLayout::new(T::layoutable_type()) -} +pub fn gpu_layout() -> TypeLayout { T::layout_recipe().layout() } /// (no documentation yet) // `CpuLayout::cpu_layout` exists, but this function exists for consistency with @@ -210,26 +216,24 @@ pub(crate) fn cpu_type_name_and_layout(ctx: &Context) -> Option<(C pub(crate) fn get_layout_compare_with_cpu_push_error( ctx: &Context, skip_stride_check: bool, -) -> GpuTypeLayout { +) -> TypeLayout { const ERR_COMMENT: &str = "`GpuLayout` uses WGSL layout rules unless #[gpu_repr(packed)] is used.\nsee https://www.w3.org/TR/WGSL/#structure-member-layout\n`CpuLayout` uses #[repr(C)].\nsee https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.struct"; - let gpu_layout = gpu_type_layout::(); + let gpu_layout = gpu_layout::(); if let Some((cpu_name, cpu_layout)) = cpu_type_name_and_layout::(ctx) { - check_layout_push_error::(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT) - .ok(); + check_layout_push_error(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT).ok(); } gpu_layout } -pub(crate) fn check_layout_push_error( +pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, cpu_layout: &TypeLayout, - gpu_layout: &GpuTypeLayout, + gpu_layout: &TypeLayout, skip_stride_check: bool, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { - let gpu_layout = &gpu_layout.layout(); type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { @@ -244,7 +248,8 @@ pub(crate) fn check_layout_push_error( }), (Some(cpu_size), Some(gpu_size)) => { let cpu_stride = cpu_size; - let gpu_stride = array_stride(gpu_layout.align(), gpu_size, T::REPR); + // + let gpu_stride = array_stride(gpu_layout.align(), gpu_size, DEFAULT_REPR); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { @@ -556,13 +561,8 @@ where } } -impl Layoutable for GpuT { - fn layoutable_type() -> layoutable::LayoutableType { todo!() } -} - impl GpuLayout for GpuT { - // fn gpu_repr() -> Repr { todo!() } - type GpuRepr = repr::Storage; + fn layout_recipe() -> layoutable::LayoutableType { todo!() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -780,13 +780,11 @@ impl CpuLayout for [T; N] { TypeLayout::new( Some(std::mem::size_of::() as u64), align, - TypeLayoutSemantics::Array( - Rc::new(ElementLayout { - byte_stride: std::mem::size_of::() as u64, - ty: T::cpu_layout(), - }), - Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), - ), + TypeLayoutSemantics::Array(Rc::new(ArrayLayout { + byte_stride: std::mem::size_of::() as u64, + element_ty: T::cpu_layout(), + len: Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), + })), ) } @@ -823,13 +821,11 @@ impl CpuLayout for [T] { TypeLayout::new( None, align, - TypeLayoutSemantics::Array( - Rc::new(ElementLayout { - byte_stride: std::mem::size_of::() as u64, - ty: T::cpu_layout(), - }), - None, - ), + TypeLayoutSemantics::Array(Rc::new(ArrayLayout { + byte_stride: std::mem::size_of::() as u64, + element_ty: T::cpu_layout(), + len: None, + })), ) } diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index 0757b77..f672ebc 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -19,7 +19,7 @@ use super::{ vec::{scalar, vec, ToInteger}, AsAny, GpuType, To, ToGpuType, }; -use crate::{any::layout::Layoutable, frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; +use crate::{frontend::rust_types::reference::Ref, ir::recording::CallInfoScope}; use crate::{ call_info, frontend::{ @@ -54,8 +54,8 @@ impl Default for mat { } } -impl Layoutable for mat { - fn layoutable_type() -> layoutable::LayoutableType { +impl GpuLayout for mat { + fn layout_recipe() -> layoutable::LayoutableType { layoutable::Matrix { columns: C::LEN2, rows: R::LEN2, @@ -63,10 +63,6 @@ impl Layoutable for mat { } .into() } -} - -impl GpuLayout for mat { - type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { None } } diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index d769701..f79f0f2 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -5,10 +5,7 @@ use std::{ }; use crate::{ - any::{ - layout::{Layoutable}, - AsAny, DataPackingFn, - }, + any::{AsAny, DataPackingFn}, common::floating_point::f16, f32x2, f32x4, gpu_layout, i32x4, u32x1, u32x4, }; @@ -25,7 +22,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{self, layoutable, repr, Repr, TypeLayout, TypeLayoutSemantics}, + type_layout::{self, layoutable, repr, Repr, TypeLayout, TypeLayoutSemantics, DEFAULT_REPR}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -133,8 +130,9 @@ impl GpuAligned for PackedVec { impl NoBools for PackedVec {} impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} -impl Layoutable for PackedVec { - fn layoutable_type() -> layoutable::LayoutableType { + +impl GpuLayout for PackedVec { + fn layout_recipe() -> layoutable::LayoutableType { layoutable::PackedVector { scalar_type: T::SCALAR_TYPE, bits_per_component: T::BITS_PER_COMPONENT, @@ -142,15 +140,11 @@ impl Layoutable for PackedVec { } .into() } -} - -impl GpuLayout for PackedVec { - type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { - let sized_ty: layoutable::SizedType = Self::layoutable_type_sized(); + let sized_ty: layoutable::SizedType = Self::layout_recipe_sized(); let name = sized_ty.to_string().into(); - let layout = TypeLayout::new_layout_for(&sized_ty.into(), Repr::Storage); + let layout = sized_ty.layout(DEFAULT_REPR); Some(Ok((name, layout))) } } diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index d8fa3f4..9c8d92d 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -1,4 +1,3 @@ -use crate::any::layout::{Layoutable}; use crate::common::small_vec::SmallVec; use crate::frontend::any::shared_io::{BindPath, BindingType}; use crate::frontend::any::{Any, InvalidReason}; @@ -135,12 +134,8 @@ impl Deref for Struct { fn deref(&self) -> &Self::Target { &self.fields } } -impl Layoutable for Struct { - fn layoutable_type() -> layoutable::LayoutableType { T::layoutable_type() } -} - impl GpuLayout for Struct { - type GpuRepr = T::GpuRepr; + fn layout_recipe() -> layoutable::LayoutableType { T::layout_recipe() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs new file mode 100644 index 0000000..1855ae2 --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -0,0 +1,447 @@ +use std::fmt::Write; + +use crate::{ + any::layout::{ElementLayout, StructLayout}, + common::prettify::set_color, + frontend::rust_types::type_layout::ArrayLayout, + TypeLayout, +}; + +use super::{layoutable::LayoutableType, Repr, DEFAULT_REPR, TypeLayoutSemantics}; + +pub struct TypeLayoutCompatibleWith { + recipe: LayoutableType, + _phantom: std::marker::PhantomData, +} + +#[derive(Debug, Clone, Copy)] +pub enum AddressSpaceEnum { + WgslStorage, + WgslUniform, +} + +impl std::fmt::Display for AddressSpaceEnum { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AddressSpaceEnum::WgslStorage => f.write_str("wgsl's storage address space"), + AddressSpaceEnum::WgslUniform => f.write_str("wgsl's uniform address space"), + } + } +} + +pub trait AddressSpace { + const ADDRESS_SPACE: AddressSpaceEnum; +} + +pub struct WgslStorage; +pub struct WgslUniform; +impl AddressSpace for WgslStorage { + const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslStorage; +} +impl AddressSpace for WgslUniform { + const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslUniform; +} + +impl AddressSpaceEnum { + fn matching_repr(&self) -> Repr { + match self { + AddressSpaceEnum::WgslStorage => Repr::Storage, + AddressSpaceEnum::WgslUniform => Repr::Uniform, + } + } +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum AddressSpaceError { + #[error("{0}")] + LayoutError(#[from] LayoutError), + #[error("Unknown layout error occured for {0} in {1}.")] + UnknownLayoutError(LayoutableType, AddressSpaceEnum), + #[error( + "The size of `{0}` on the gpu is not known at compile time. {1} \ + requires that the size of {0} on the gpu is known at compile time." + )] + MustBeSized(LayoutableType, AddressSpaceEnum), + #[error("{0} contains a `PackedVector`, which are not allowed in {1}.")] + MayNotContainPackedVec(LayoutableType, AddressSpaceEnum), +} + +#[derive(Debug, Clone)] +pub struct LayoutError { + recipe: LayoutableType, + address_space: AddressSpaceEnum, + mismatch: LayoutMismatch, +} + +impl std::error::Error for LayoutError {} +impl std::fmt::Display for LayoutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use TypeLayoutSemantics::*; + + let types_are_the_same = "The LayoutError is produced by comparing two semantically equivalent TypeLayout, so all (nested) types are the same"; + match &self.mismatch { + LayoutMismatch::TopLevel { + layout1, + layout2, + mismatch, + } => match mismatch { + TopLevelMismatch::Type => unreachable!("{}", types_are_the_same), + TopLevelMismatch::ArrayStride => { + let (element1, element2) = match (&layout1.kind, &layout2.kind) { + (Array(e1, _), Array(e2, _)) => (e1, e2), + _ => unreachable!("Array stride error can only occur if the layouts are arrays"), + }; + + writeln!( + f, + "`{}` requires a stride of {} in {}, but has a stride of {}.", + layout1.to_string_plain(), + element2.byte_stride, + self.address_space, + element1.byte_stride + )?; + } + }, + LayoutMismatch::Struct { + layout1, + layout2, + mismatch, + } => { + let field_index = match mismatch { + StructMismatch::FieldArrayStride { + field_index, + array_left, + array_right, + } => { + writeln!( + f, + "`{}` in {} requires a stride of {} in {}, but has a stride of {}.", + *layout1.name, array_left.byte_stride, self.address_space, array_right.byte_stride + )?; + } + StructMismatch::FieldCount => unreachable!("{}", types_are_the_same), + }; + } + } + todo!() + } +} + +impl TypeLayoutCompatibleWith { + pub fn try_from(recipe: LayoutableType) -> Result { + let address_space = AS::ADDRESS_SPACE; + let layout = recipe.layout(); + + match (address_space, layout.byte_size()) { + // Must be sized in wgsl's uniform address space + (AddressSpaceEnum::WgslUniform, None) => return Err(AddressSpaceError::MustBeSized(recipe, address_space)), + (AddressSpaceEnum::WgslUniform, Some(_)) | (AddressSpaceEnum::WgslStorage, _) => {} + } + + // Check for layout errors + let recipe_unified = recipe.to_unified_repr(address_space.matching_repr()); + let layout_unified = recipe_unified.layout(); + if layout != layout_unified { + match try_find_mismatch(&layout, &layout_unified) { + Some(mismatch) => { + return Err(LayoutError { + recipe, + address_space, + mismatch, + } + .into()); + } + None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), + } + } + + Ok(Self { + recipe, + _phantom: std::marker::PhantomData, + }) + } +} + +#[derive(Debug, Clone)] +pub enum LayoutMismatch { + /// `layout1` and `layout2` are always top level layouts. + /// + /// For example, in case of `Array>`, it's always the layout + /// of `Array>` even if the array stride mismatch + /// may be happening for the inner `Array`. + TopLevel { + layout1: TypeLayout, + layout2: TypeLayout, + mismatch: TopLevelMismatch, + }, + Struct { + layout1: StructLayout, + layout2: StructLayout, + mismatch: StructMismatch, + }, +} + +#[derive(Debug, Clone)] +pub enum TopLevelMismatch { + Type, + /// This contains the array layout where the mismatch is happening, + /// which is not necessarily the top level array. + /// For example, in case of an array stride mismatch of the inner + /// array in Array>, this is Array's layout. + ArrayStride { + array_left: ArrayLayout, + array_right: ArrayLayout, + }, +} + +#[derive(Debug, Clone)] +pub enum StructMismatch { + FieldCount, + FieldType { + field_index: usize, + }, + FieldOffset { + field_index: usize, + }, + FieldByteSize { + field_index: usize, + }, + FieldArrayStride { + field_index: usize, + array_left: ArrayLayout, + array_right: ArrayLayout, + }, +} + +fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { + use TypeLayoutSemantics::*; + + // First check if the kinds are the same type + match (&layout1.kind, &layout2.kind) { + (Vector(v1), Vector(v2)) => { + if v1 != v2 { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch: TopLevelMismatch::Type, + }); + } + } + (PackedVector(p1), PackedVector(p2)) => { + if p1 != p2 { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch: TopLevelMismatch::Type, + }); + } + } + (Matrix(m1), Matrix(m2)) => { + if m1 != m2 { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch: TopLevelMismatch::Type, + }); + } + } + (Array(a1), Array(a2)) => { + // Check array sizes match + if a1.len != a2.len { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch: TopLevelMismatch::Type, + }); + } + + // Check array stride + if a1.byte_stride != a2.byte_stride { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch: TopLevelMismatch::ArrayStride { + array_left: (**a1).clone(), + array_right: (**a2).clone(), + }, + }); + } + + // Recursively check element types + match try_find_mismatch(&a1.element_ty, &a2.element_ty) { + // In case the mismatch isn't related to a struct field mismatch, + // we propagate the error upwards to this array. For example + // Array> vs Array> comparison returns a + // Array> vs Array> type mismatch and not a + // Array vs Array mismatch + // Note that we don't change the TopLevelMismatch::ArrayStride fields though. + Some(LayoutMismatch::TopLevel { + layout1, + layout2, + mismatch, + }) => match mismatch { + TopLevelMismatch::Type | TopLevelMismatch::ArrayStride { .. } => { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch, + }); + } + }, + m @ Some(LayoutMismatch::Struct { .. }) => return m, + None => return None, + } + } + (Structure(s1), Structure(s2)) => { + return try_find_struct_mismatch(s1, s2); + } + // Different kinds entirely. Matching exhaustively, so that changes to TypeLayout lead us here. + (Vector(_) | PackedVector(_) | Matrix(_) | Array(_) | Structure(_), _) => { + return Some(LayoutMismatch::TopLevel { + layout1: layout1.clone(), + layout2: layout2.clone(), + mismatch: TopLevelMismatch::Type, + }); + } + } + + None +} + +fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> Option { + // Check field count + if struct1.fields.len() != struct2.fields.len() { + return Some(LayoutMismatch::Struct { + layout1: struct1.clone(), + layout2: struct2.clone(), + mismatch: StructMismatch::FieldCount, + }); + } + + for (field_index, (field1, field2)) in struct1.fields.iter().zip(struct2.fields.iter()).enumerate() { + // Check field offset + if field1.rel_byte_offset != field2.rel_byte_offset { + return Some(LayoutMismatch::Struct { + layout1: struct1.clone(), + layout2: struct2.clone(), + mismatch: StructMismatch::FieldOffset { field_index }, + }); + } + + // Check field byte size + if field1.field.ty.byte_size != field2.field.ty.byte_size { + return Some(LayoutMismatch::Struct { + layout1: struct1.clone(), + layout2: struct2.clone(), + mismatch: StructMismatch::FieldByteSize { field_index }, + }); + } + + // Recursively check field types + if let Some(inner_mismatch) = try_find_mismatch(&field1.field.ty, &field2.field.ty) { + match inner_mismatch { + // If it's a top-level mismatch, convert it to a field mismatch + LayoutMismatch::TopLevel { + mismatch: TopLevelMismatch::Type, + .. + } => { + return Some(LayoutMismatch::Struct { + layout1: struct1.clone(), + layout2: struct2.clone(), + mismatch: StructMismatch::FieldType { field_index }, + }); + } + LayoutMismatch::TopLevel { + mismatch: + TopLevelMismatch::ArrayStride { + array_left, + array_right, + }, + .. + } => { + return Some(LayoutMismatch::Struct { + layout1: struct1.clone(), + layout2: struct2.clone(), + mismatch: StructMismatch::FieldArrayStride { + field_index, + array_left, + array_right, + }, + }); + } + // Pass through nested struct mismatches + struct_mismatch @ LayoutMismatch::Struct { .. } => return Some(struct_mismatch), + } + } + } + + None +} + +#[derive(Debug, Clone, PartialEq)] +pub enum DiffLine { + Shared(String), + Left(String), + Right(String), +} + +pub fn diff_lines(left: &str, right: &str) -> Vec { + let left_lines: Vec<&str> = left.lines().collect(); + let right_lines: Vec<&str> = right.lines().collect(); + + let mut result = Vec::new(); + let mut left_idx = 0; + let mut right_idx = 0; + + while left_idx < left_lines.len() && right_idx < right_lines.len() { + if left_lines[left_idx] == right_lines[right_idx] { + result.push(DiffLine::Shared(left_lines[left_idx].to_string())); + left_idx += 1; + right_idx += 1; + } else { + // Find the next matching line + let mut found_match = false; + for i in (left_idx + 1)..left_lines.len() { + if let Some(j) = right_lines[(right_idx + 1)..] + .iter() + .position(|&line| line == left_lines[i]) + { + let right_match_idx = right_idx + 1 + j; + + // Add differing lines before the match + for k in left_idx..i { + result.push(DiffLine::Left(left_lines[k].to_string())); + } + for k in right_idx..right_match_idx { + result.push(DiffLine::Right(right_lines[k].to_string())); + } + + left_idx = i; + right_idx = right_match_idx; + found_match = true; + break; + } + } + + if !found_match { + result.push(DiffLine::Left(left_lines[left_idx].to_string())); + result.push(DiffLine::Right(right_lines[right_idx].to_string())); + left_idx += 1; + right_idx += 1; + } + } + } + + // Add remaining lines + while left_idx < left_lines.len() { + result.push(DiffLine::Left(left_lines[left_idx].to_string())); + left_idx += 1; + } + + while right_idx < right_lines.len() { + result.push(DiffLine::Right(right_lines[right_idx].to_string())); + right_idx += 1; + } + + result +} diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs index ec590e6..2aae5b6 100644 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ b/shame/src/frontend/rust_types/type_layout/construction.rs @@ -13,122 +13,6 @@ use super::{ }; use TypeLayoutSemantics as TLS; -impl TypeLayout { - /// Returns the type layout of the `LayoutableType` - /// layed out according to the `repr`. - pub fn new_layout_for(ty: &LayoutableType, repr: Repr) -> Self { - match ty { - LayoutableType::Sized(ty) => Self::from_sized_type(ty, repr), - LayoutableType::UnsizedStruct(ty) => Self::from_unsized_struct(ty, repr), - LayoutableType::RuntimeSizedArray(ty) => Self::from_runtime_sized_array(ty, repr), - } - } - - fn from_sized_type(ty: &SizedType, repr: Repr) -> Self { - let (size, align, tls) = match &ty { - SizedType::Vector(v) => (v.byte_size(repr), v.align(repr), TLS::Vector(*v)), - SizedType::Atomic(a) => ( - a.byte_size(), - a.align(repr), - TLS::Vector(Vector::new(a.scalar.into(), ir::Len::X1)), - ), - SizedType::Matrix(m) => (m.byte_size(repr), m.align(repr), TLS::Matrix(*m)), - SizedType::Array(a) => ( - a.byte_size(repr), - a.align(repr), - TLS::Array( - Rc::new(ElementLayout { - byte_stride: a.byte_stride(repr), - ty: Self::from_sized_type(&a.element, repr), - }), - Some(a.len.get()), - ), - ), - SizedType::PackedVec(v) => (v.byte_size().as_u64(), v.align(repr), TLS::PackedVector(*v)), - SizedType::Struct(s) => { - let mut field_offsets = s.field_offsets(repr); - let fields = (&mut field_offsets) - .zip(s.fields()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) - .collect(); - - let (byte_size, align) = field_offsets.struct_byte_size_and_align(); - ( - byte_size, - align, - TLS::Structure(Rc::new(StructLayout { - name: s.name.clone().into(), - fields, - })), - ) - } - }; - - TypeLayout::new(Some(size), align, tls) - } - - fn from_unsized_struct(s: &UnsizedStruct, repr: Repr) -> Self { - let mut field_offsets = s.field_offsets(repr); - let mut fields = (&mut field_offsets.sized_field_offsets()) - .zip(s.sized_fields.iter()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, repr)) - .collect::>(); - - let (field_offset, align) = field_offsets.last_field_offset_and_struct_align(); - - let mut ty = Self::from_runtime_sized_array(&s.last_unsized.array, repr); - // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account - // custom_min_align, but s.last_unsized.align does. - ty.align = s.last_unsized.align(repr).into(); - - fields.push(FieldLayoutWithOffset { - rel_byte_offset: field_offset, - field: FieldLayout { - name: s.last_unsized.name.clone(), - ty, - }, - }); - - TypeLayout::new( - None, - align, - TLS::Structure(Rc::new(StructLayout { - name: s.name.clone().into(), - fields, - })), - ) - } - - fn from_runtime_sized_array(ty: &RuntimeSizedArray, repr: Repr) -> Self { - Self::new( - None, - ty.align(repr), - TLS::Array( - Rc::new(ElementLayout { - byte_stride: ty.byte_stride(repr), - ty: Self::from_sized_type(&ty.element, repr), - }), - None, - ), - ) - } -} - -fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { - let mut ty = TypeLayout::from_sized_type(&field.ty, repr); - // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account - // custom_min_align and custom_min_size, but field.byte_size and field.align do. - ty.byte_size = Some(field.byte_size(repr)); - ty.align = field.align(repr).into(); - FieldLayoutWithOffset { - rel_byte_offset: offset, - field: FieldLayout { - name: field.name.clone(), - ty, - }, - } -} - impl GpuTypeLayout { /// Creates a new `GpuTypeLayout` where `T` implements [`DerivableRepr`]. /// diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index a7c8275..ccbdebb 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -311,8 +311,8 @@ impl LayoutMismatch { // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl return Ok(KeepWriting); } - (S::Array(ta, na), S::Array(tb, nb)) => { - if na != nb { + (S::Array(a), S::Array(b)) => { + if a.len != b.len { writeln!(f); color_a(f); write!(f, "{a_name}{SEP}"); @@ -320,7 +320,7 @@ impl LayoutMismatch { write!( f, "array<…, {}>", - match na { + match a.len { Some(n) => n as &dyn Display, None => (&"runtime-sized") as &dyn Display, } @@ -334,7 +334,7 @@ impl LayoutMismatch { write!( f, "array<…, {}>", - match nb { + match b.len { Some(n) => n as &dyn Display, None => (&"runtime-sized") as &dyn Display, } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 642cbe6..eb7e942 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -8,65 +8,81 @@ pub(crate) const PACKED_ALIGN: U32PowerOf2 = U32PowerOf2::_1; impl LayoutableType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn byte_size(&self, repr: Repr) -> Option { + pub fn byte_size(&self, default_repr: Repr) -> Option { match self { - LayoutableType::Sized(s) => Some(s.byte_size(repr)), + LayoutableType::Sized(s) => Some(s.byte_size(default_repr)), LayoutableType::UnsizedStruct(_) | LayoutableType::RuntimeSizedArray(_) => None, } } - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. + pub fn align(&self, default_repr: Repr) -> U32PowerOf2 { match self { - LayoutableType::Sized(s) => s.align(repr), - LayoutableType::UnsizedStruct(s) => s.align(repr), - LayoutableType::RuntimeSizedArray(a) => a.align(repr), + LayoutableType::Sized(s) => s.align(default_repr), + LayoutableType::UnsizedStruct(s) => s.align(), + LayoutableType::RuntimeSizedArray(a) => a.align(default_repr), } } /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. - pub fn byte_size_and_align(&self, repr: Repr) -> (Option, U32PowerOf2) { + pub fn byte_size_and_align(&self, default_repr: Repr) -> (Option, U32PowerOf2) { match self { LayoutableType::Sized(s) => { - let (size, align) = s.byte_size_and_align(repr); + let (size, align) = s.byte_size_and_align(default_repr); (Some(size), align) } - LayoutableType::UnsizedStruct(s) => (None, s.align(repr)), - LayoutableType::RuntimeSizedArray(a) => (None, a.align(repr)), + LayoutableType::UnsizedStruct(s) => (None, s.align()), + LayoutableType::RuntimeSizedArray(a) => (None, a.align(default_repr)), } } -} -impl SizedType { - /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn byte_size(&self, repr: Repr) -> u64 { + pub fn to_unified_repr(&self, repr: Repr) -> Self { + let mut this = self.clone(); + this.change_all_repr(repr); + this + } + + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { match self { - SizedType::Array(a) => a.byte_size(repr), - SizedType::Vector(v) => v.byte_size(repr), - SizedType::Matrix(m) => m.byte_size(repr), - SizedType::Atomic(a) => a.byte_size(), - SizedType::PackedVec(v) => u8::from(v.byte_size()) as u64, - SizedType::Struct(s) => s.byte_size_and_align(repr).0, + LayoutableType::Sized(s) => s.change_all_repr(repr), + LayoutableType::UnsizedStruct(s) => s.change_all_repr(repr), + LayoutableType::RuntimeSizedArray(a) => a.change_all_repr(repr), } } +} +impl SizedType { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { + pub fn byte_size(&self, parent_repr: Repr) -> u64 { self.byte_size_and_align(parent_repr).0 } + + /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. + pub fn align(&self, parent_repr: Repr) -> U32PowerOf2 { self.byte_size_and_align(parent_repr).1 } + + /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. + pub fn byte_size_and_align(&self, parent_repr: Repr) -> (u64, U32PowerOf2) { + let repr = parent_repr; match self { - SizedType::Array(a) => a.align(repr), - SizedType::Vector(v) => v.align(repr), - SizedType::Matrix(m) => m.align(repr), - SizedType::Atomic(a) => a.align(repr), - SizedType::PackedVec(v) => v.align(repr), - SizedType::Struct(s) => s.byte_size_and_align(repr).1, + SizedType::Array(a) => (a.byte_size(parent_repr), a.align(parent_repr)), + SizedType::Vector(v) => (v.byte_size(parent_repr), v.align(parent_repr)), + SizedType::Matrix(m) => (m.byte_size(parent_repr), m.align(parent_repr)), + SizedType::Atomic(a) => (a.byte_size(), a.align(parent_repr)), + SizedType::PackedVec(v) => (u8::from(v.byte_size()) as u64, v.align(parent_repr)), + SizedType::Struct(s) => s.byte_size_and_align(), } } - /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. - pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { match self { - SizedType::Struct(s) => s.byte_size_and_align(repr), - non_struct => (non_struct.byte_size(repr), non_struct.align(repr)), + SizedType::Struct(s) => s.change_all_repr(repr), + SizedType::Atomic(_) | + SizedType::PackedVec(_) | + SizedType::Vector(_) | + SizedType::Matrix(_) | + SizedType::Array(_) => { + // No repr to change for these types. + } } } } @@ -75,15 +91,19 @@ impl SizedStruct { /// Returns [`FieldOffsetsSized`], which serves as an iterator over the offsets of the /// fields of this struct. `FieldOffsetsSized::struct_byte_size_and_align` can be /// used to efficiently obtain the byte_size and align. - pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsSized { - FieldOffsetsSized(FieldOffsets::new(self.fields(), repr)) - } + pub fn field_offsets(&self) -> FieldOffsetsSized { FieldOffsetsSized(FieldOffsets::new(self.fields(), self.repr)) } /// Returns (byte_size, align) /// /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. - pub fn byte_size_and_align(&self, repr: Repr) -> (u64, U32PowerOf2) { - self.field_offsets(repr).struct_byte_size_and_align() + pub fn byte_size_and_align(&self) -> (u64, U32PowerOf2) { self.field_offsets().struct_byte_size_and_align() } + + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { + self.repr = repr; + for field in &mut self.fields { + field.ty.change_all_repr(repr); + } } } @@ -181,12 +201,21 @@ impl UnsizedStruct { /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset /// and the struct's align - pub fn field_offsets(&self, repr: Repr) -> FieldOffsetsUnsized { - FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, repr) + pub fn field_offsets(&self) -> FieldOffsetsUnsized { + FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, self.repr) } /// This is expensive as it calculates the byte align by traversing all fields recursively. - pub fn align(&self, repr: Repr) -> U32PowerOf2 { self.field_offsets(repr).last_field_offset_and_struct_align().1 } + pub fn align(&self) -> U32PowerOf2 { self.field_offsets().last_field_offset_and_struct_align().1 } + + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { + self.repr = repr; + for field in &mut self.sized_fields { + field.ty.change_all_repr(repr); + } + self.last_unsized.array.change_all_repr(repr); + } } #[allow(missing_docs)] @@ -299,6 +328,13 @@ impl SizedArray { let (element_size, element_align) = self.element.byte_size_and_align(repr); array_stride(element_align, element_size, repr) } + + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { + let mut element = (*self.element).clone(); + element.change_all_repr(repr); + self.element = Rc::new(element); + } } /// Returns an array's size given it's stride and length. @@ -334,9 +370,18 @@ pub const fn array_stride(element_align: U32PowerOf2, element_size: u64, repr: R #[allow(missing_docs)] impl RuntimeSizedArray { - pub fn align(&self, repr: Repr) -> U32PowerOf2 { array_align(self.element.align(repr), repr) } + pub fn align(&self, parent_repr: Repr) -> U32PowerOf2 { array_align(self.element.align(parent_repr), parent_repr) } + + pub fn byte_stride(&self, parent_repr: Repr) -> u64 { + array_stride( + self.align(parent_repr), + self.element.byte_size(parent_repr), + parent_repr, + ) + } - pub fn byte_stride(&self, repr: Repr) -> u64 { array_stride(self.align(repr), self.element.byte_size(repr), repr) } + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { self.element.change_all_repr(repr); } } #[allow(missing_docs)] @@ -345,8 +390,6 @@ impl SizedField { StructLayoutCalculator::calculate_byte_size(self.ty.byte_size(repr), self.custom_min_size) } pub fn align(&self, repr: Repr) -> U32PowerOf2 { - // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. - // This is intended! StructLayoutCalculator::calculate_align(self.ty.align(repr), self.custom_min_align, repr) } } @@ -354,8 +397,6 @@ impl SizedField { #[allow(missing_docs)] impl RuntimeSizedArrayField { pub fn align(&self, repr: Repr) -> U32PowerOf2 { - // In case of Repr::Packed, the field's align of 1 is overwritten here by custom_min_align. - // This is intended! StructLayoutCalculator::calculate_align(self.array.align(repr), self.custom_min_align, repr) } } @@ -978,6 +1019,7 @@ mod tests { "TestStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes, 4-byte aligned + Repr::Storage, ) .extend( "field2", @@ -989,7 +1031,7 @@ mod tests { ); // Test field offsets - let mut field_offsets = sized_struct.field_offsets(Repr::Storage); + let mut field_offsets = sized_struct.field_offsets(); // Field 1: offset 0 assert_eq!(field_offsets.next(), Some(0)); @@ -1001,7 +1043,7 @@ mod tests { assert_eq!(field_offsets.next(), None); // Test struct size and alignment - let (size, align) = sized_struct.byte_size_and_align(Repr::Storage); + let (size, align) = sized_struct.byte_size_and_align(); assert_eq!(size, 24); // Round up to 8-byte alignment: round_up(8, 20) = 24 assert_eq!(align, U32PowerOf2::_8); } @@ -1012,9 +1054,10 @@ mod tests { "TestStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 8 bytes, 8-byte aligned + Repr::Uniform, ); - let (size, align) = sized_struct.byte_size_and_align(Repr::Uniform); + let (size, align) = sized_struct.byte_size_and_align(); assert_eq!(align, U32PowerOf2::_16); // Alignment adjusted for uniform to multiple of 16 assert_eq!(size, 16); // Byte size of struct is a multiple of it's alignment @@ -1023,10 +1066,11 @@ mod tests { #[test] fn test_unsized_struct_layout() { // Test UnsizedStruct with sized fields and a runtime sized array - let unsized_struct = SizedStruct::new( + let mut unsized_struct = SizedStruct::new( "UnsizedStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 4 bytes, 4-byte aligned + Repr::Storage, ) .extend( "field2", @@ -1039,7 +1083,7 @@ mod tests { ); // Test field offsets - let mut field_offsets = unsized_struct.field_offsets(Repr::Storage); + let mut field_offsets = unsized_struct.field_offsets(); let sized_offsets: Vec = field_offsets.sized_field_offsets().collect(); assert_eq!(sized_offsets, vec![0, 8]); // First field at 0, second at 8 @@ -1049,10 +1093,11 @@ mod tests { assert_eq!(struct_align, U32PowerOf2::_8); // Struct alignment is 8 // Test struct alignment method - assert_eq!(unsized_struct.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(unsized_struct.align(), U32PowerOf2::_8); // Test with different repr - let mut field_offsets_uniform = unsized_struct.field_offsets(Repr::Uniform); + unsized_struct.change_all_repr(Repr::Uniform); + let mut field_offsets_uniform = unsized_struct.field_offsets(); let (last_offset_uniform, struct_align_uniform) = field_offsets_uniform.last_field_offset_and_struct_align(); assert_eq!(last_offset_uniform, 16); // Different offset in uniform, because array's alignment is 16 assert_eq!(struct_align_uniform, U32PowerOf2::_16); // Uniform struct alignment @@ -1064,20 +1109,21 @@ mod tests { "TestStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X3)), // 8 bytes, 16 align (when not packed) + Repr::Packed, ) .extend( "field2", SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes ); - let mut field_offsets = sized_struct.field_offsets(Repr::Packed); + let mut field_offsets = sized_struct.field_offsets(); // Field 1: offset 0 assert_eq!(field_offsets.next(), Some(0)); // Field 2: offset 12, packed directly after field 1, despite 16 alignment of field1, because packed assert_eq!(field_offsets.next(), Some(12)); - let (size, align) = sized_struct.byte_size_and_align(Repr::Packed); + let (size, align) = sized_struct.byte_size_and_align(); assert_eq!(size, 16); assert_eq!(align, U32PowerOf2::_1); } @@ -1098,12 +1144,19 @@ mod tests { let s = SizedStruct::new( "TestStruct", - FieldOptions::new("field1", Some(U32PowerOf2::_16), None), + "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 8 bytes, 8-byte aligned + Repr::Packed, + ) + .extend( + FieldOptions::new("field2", Some(U32PowerOf2::_16), None), + SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes, 4-byte aligned ); // The custom min align is ignored in packed structs - assert_eq!(s.byte_size_and_align(Repr::Packed).1, U32PowerOf2::_1); - assert_eq!(s.fields()[0].align(Repr::Packed), U32PowerOf2::_1); + assert_eq!(s.byte_size_and_align().1, U32PowerOf2::_1); + let mut offsets = s.field_offsets(); + assert_eq!(offsets.next(), Some(0)); // field1 at offset 0 + assert_eq!(offsets.next(), Some(8)); // field2 at offset 8, because min align is ignored } } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs index ea6e07f..5af2662 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs @@ -10,6 +10,7 @@ impl LayoutableType { pub fn struct_from_parts( struct_name: impl Into, fields: impl IntoIterator, + repr: Repr, ) -> Result { use StructFromPartsError::*; @@ -58,10 +59,11 @@ impl LayoutableType { name: struct_name.into(), sized_fields, last_unsized, + repr, } .into()) } else { - Ok(SizedStruct::from_parts(struct_name, sized_fields).into()) + Ok(SizedStruct::from_parts(struct_name, sized_fields, repr).into()) } } } @@ -81,10 +83,16 @@ impl SizedStruct { /// Creates a new `SizedStruct` with one field. /// /// To add additional fields to it, use [`SizedStruct::extend`] or [`SizedStruct::extend_unsized`]. - pub fn new(name: impl Into, field_options: impl Into, ty: impl Into) -> Self { + pub fn new( + name: impl Into, + field_options: impl Into, + ty: impl Into, + repr: Repr, + ) -> Self { Self { name: name.into(), fields: vec![SizedField::new(field_options, ty)], + repr, } } @@ -106,6 +114,7 @@ impl SizedStruct { name: self.name, sized_fields: self.fields, last_unsized: RuntimeSizedArrayField::new(name, custom_min_align, element_ty), + repr: self.repr, } } @@ -126,10 +135,11 @@ impl SizedStruct { /// The fields of this struct. pub fn fields(&self) -> &[SizedField] { &self.fields } - pub(crate) fn from_parts(name: impl Into, fields: Vec) -> Self { + pub(crate) fn from_parts(name: impl Into, fields: Vec, repr: Repr) -> Self { Self { name: name.into(), fields, + repr, } } } @@ -210,7 +220,7 @@ impl RuntimeSizedArray { /// /// If you only want to customize the field's name, you can convert most string types /// to `FieldOptions` using `Into::into`. For methods that take `impl Into` -/// parameters you can just pass the string type directly. +/// parameters you can just pass the string type directly. #[derive(Debug, Clone)] pub struct FieldOptions { /// Name of the field diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index 66ba67d..c2e6321 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -1,3 +1,5 @@ +use crate::GpuLayout; + use super::*; // Conversions to ir types // @@ -285,6 +287,9 @@ impl TryFrom for SizedStruct { Ok(SizedStruct { name: structure.name().clone(), fields, + // TODO(chronicl) hardcoding this is a temporary solution. This whole + // TryFrom should be removed in future PRs. + repr: Repr::Storage, }) } } @@ -335,6 +340,9 @@ impl TryFrom for LayoutableType { return Ok(SizedStruct { name: buffer_block.name().clone(), fields: sized_fields, + // TODO(chronicl) hardcoding this is a temporary solution. This whole + // TryFrom should be removed in future PRs. + repr: Repr::Storage, } .into()); }; @@ -343,6 +351,9 @@ impl TryFrom for LayoutableType { name: buffer_block.name().clone(), sized_fields, last_unsized, + // TODO(chronicl) hardcoding this is a temporary solution. This whole + // TryFrom should be removed in future PRs. + repr: Repr::Storage, } .into()) } @@ -353,9 +364,9 @@ impl TryFrom for LayoutableType { fn test_ir_conversion_error() { use crate::{f32x1, packed::unorm8x2}; - let ty: LayoutableType = SizedStruct::new("A", "a", f32x1::layoutable_type_sized()) - .extend("b", f32x1::layoutable_type_sized()) - .extend("a", f32x1::layoutable_type_sized()) + let ty: LayoutableType = SizedStruct::new("A", "a", f32x1::layout_recipe_sized(), Repr::Storage) + .extend("b", f32x1::layout_recipe_sized()) + .extend("a", f32x1::layout_recipe_sized()) .into(); let result: Result = ty.try_into(); assert!(matches!( @@ -368,7 +379,7 @@ fn test_ir_conversion_error() { })) )); - let ty: LayoutableType = SizedStruct::new("A", "a", unorm8x2::layoutable_type_sized()).into(); + let ty: LayoutableType = SizedStruct::new("A", "a", unorm8x2::layout_recipe_sized(), Repr::Storage).into(); let result: Result = ty.try_into(); assert!(matches!(result, Err(IRConversionError::ContainsPackedVector))); } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index 73de0e7..f9471c7 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -11,11 +11,12 @@ use crate::{ }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{construction::StructKind}; +use super::{construction::StructKind, Repr}; pub(crate) mod align_size; pub(crate) mod builder; pub(crate) mod ir_compat; +pub(crate) mod to_layout; pub use align_size::{FieldOffsets, MatrixMajor, StructLayoutCalculator, array_size, array_stride, array_align}; pub use builder::{SizedOrArray, FieldOptions}; @@ -103,6 +104,8 @@ pub struct SizedStruct { pub name: CanonName, // This is private to ensure a `SizedStruct` always has at least one field. fields: Vec, + /// The representation/layout rules for this struct. See [`Repr`] for more details. + pub repr: Repr, } /// A struct whose size is not known before shader runtime. @@ -116,6 +119,8 @@ pub struct UnsizedStruct { pub sized_fields: Vec, /// Last runtime sized array field of the struct. pub last_unsized: RuntimeSizedArrayField, + /// The representation/layout rules for this struct. See [`Repr`] for more details. + pub repr: Repr, } #[allow(missing_docs)] @@ -135,26 +140,6 @@ pub struct RuntimeSizedArrayField { pub array: RuntimeSizedArray, } -/// Trait for types that have a well-defined memory layout. -pub trait Layoutable { - /// Returns the `LayoutableType` representation for this type. - fn layoutable_type() -> LayoutableType; - - /// When the type is `GpuSized`, this method can be used to immediately get - /// the `LayoutableType::Sized` variant's inner `SizedType`. - fn layoutable_type_sized() -> SizedType - where - Self: GpuSized, - { - match Self::layoutable_type() { - LayoutableType::Sized(s) => s, - LayoutableType::RuntimeSizedArray(_) | LayoutableType::UnsizedStruct(_) => { - unreachable!("Self is GpuSized, which these LayoutableType variants aren't.") - } - } - } -} - // Conversions to ScalarType, SizedType and LayoutableType // macro_rules! impl_into_sized_type { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs b/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs new file mode 100644 index 0000000..4fc523f --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs @@ -0,0 +1,177 @@ +use std::rc::Rc; + +use crate::{ + any::layout::{ElementLayout, FieldLayout, FieldLayoutWithOffset, Repr, StructLayout, TypeLayoutSemantics}, + frontend::rust_types::type_layout::{ArrayLayout, DEFAULT_REPR}, + ir, TypeLayout, +}; + +use super::{ + Atomic, LayoutableType, Matrix, PackedVector, RuntimeSizedArray, SizedArray, SizedField, SizedStruct, SizedType, + UnsizedStruct, Vector, +}; + +impl LayoutableType { + pub fn layout(&self) -> TypeLayout { self.layout_with_default_repr(DEFAULT_REPR) } + + pub fn layout_with_default_repr(&self, default_repr: Repr) -> TypeLayout { + match self { + LayoutableType::Sized(ty) => ty.layout(default_repr), + LayoutableType::UnsizedStruct(ty) => ty.layout(default_repr), + LayoutableType::RuntimeSizedArray(ty) => ty.layout(default_repr), + } + } +} + +impl SizedType { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + match &self { + SizedType::Vector(v) => v.layout(parent_repr), + SizedType::Atomic(a) => a.layout(parent_repr), + SizedType::Matrix(m) => m.layout(parent_repr), + SizedType::Array(a) => a.layout(parent_repr), + SizedType::PackedVec(v) => v.layout(parent_repr), + SizedType::Struct(s) => s.layout(parent_repr), + } + } +} + +impl Vector { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + TypeLayout::new( + self.byte_size(parent_repr).into(), + self.align(parent_repr), + TypeLayoutSemantics::Vector(*self), + ) + } +} + +impl Matrix { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + TypeLayout::new( + self.byte_size(parent_repr).into(), + self.align(parent_repr), + TypeLayoutSemantics::Matrix(*self), + ) + } +} + +impl Atomic { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + // Atomic types are represented as vectors of length 1. + let vector = Vector::new(self.scalar.into(), ir::Len::X1); + TypeLayout::new( + vector.byte_size(parent_repr).into(), + vector.align(parent_repr), + TypeLayoutSemantics::Vector(vector), + ) + } +} + +impl SizedArray { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + TypeLayout::new( + self.byte_size(parent_repr).into(), + self.align(parent_repr), + TypeLayoutSemantics::Array(Rc::new(ArrayLayout { + byte_stride: self.byte_stride(parent_repr), + element_ty: self.element.layout(parent_repr), + len: Some(self.len.get()), + })), + ) + } +} + +impl PackedVector { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + TypeLayout::new( + self.byte_size().as_u64().into(), + self.align(parent_repr), + TypeLayoutSemantics::PackedVector(*self), + ) + } +} + +impl SizedStruct { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + let mut field_offsets = self.field_offsets(); + let fields = (&mut field_offsets) + .zip(self.fields()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, parent_repr)) + .collect::>(); + + let (byte_size, align) = field_offsets.struct_byte_size_and_align(); + + TypeLayout::new( + Some(byte_size), + align, + TypeLayoutSemantics::Structure(Rc::new(StructLayout { + name: self.name.clone().into(), + fields, + })), + ) + } +} + +fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { + let mut ty = field.ty.layout(repr); + // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account + // custom_min_align and custom_min_size, but field.byte_size and field.align do. + ty.byte_size = Some(field.byte_size(repr)); + ty.align = field.align(repr).into(); + FieldLayoutWithOffset { + rel_byte_offset: offset, + field: FieldLayout { + name: field.name.clone(), + ty, + }, + } +} + +impl UnsizedStruct { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + let mut field_offsets = self.field_offsets(); + let mut fields = (&mut field_offsets.sized_field_offsets()) + .zip(self.sized_fields.iter()) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, parent_repr)) + .collect::>(); + + let (field_offset, align) = field_offsets.last_field_offset_and_struct_align(); + + let mut ty = self.last_unsized.array.layout(parent_repr); + // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account + // custom_min_align, but s.last_unsized.align does. + ty.align = self.last_unsized.align(parent_repr).into(); + + fields.push(FieldLayoutWithOffset { + rel_byte_offset: field_offset, + field: FieldLayout { + name: self.last_unsized.name.clone(), + ty, + }, + }); + + TypeLayout::new( + None, + align, + TypeLayoutSemantics::Structure(Rc::new(StructLayout { + name: self.name.clone().into(), + fields, + })), + ) + } +} + +impl RuntimeSizedArray { + pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + TypeLayout::new( + None, + self.align(parent_repr), + TypeLayoutSemantics::Array(Rc::new(ArrayLayout { + byte_stride: self.byte_stride(parent_repr), + element_ty: self.element.layout(parent_repr), + len: None, + })), + ) + } +} diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 69bca48..f2f5ff9 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -8,7 +8,7 @@ use std::{ }; use crate::{ - any::U32PowerOf2, + any::{layout::repr::Packed, U32PowerOf2}, call_info, common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, ir::{ @@ -20,13 +20,16 @@ use crate::{ }; use layoutable::{ align_size::{StructLayoutCalculator, PACKED_ALIGN}, - LayoutableType, Matrix, Vector, + LayoutableType, Matrix, Vector, PackedVector, }; +pub(crate) mod compatible_with; pub(crate) mod construction; pub(crate) mod eq; pub(crate) mod layoutable; +pub const DEFAULT_REPR: Repr = Repr::Storage; + /// The type contained in the bytes of a `TypeLayout`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TypeLayoutSemantics { @@ -35,15 +38,46 @@ pub enum TypeLayoutSemantics { /// special compressed vectors for vertex attribute types /// /// see the [`crate::packed`] module - PackedVector(ir::PackedVector), + PackedVector(PackedVector), /// `mat`, first `Len2` is cols, 2nd `Len2` is rows Matrix(Matrix), /// `Array` and `Array>` - Array(Rc, Option), // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. + Array(Rc), /// structures which may be empty and may have an unsized last field Structure(Rc), } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VectorLayout { + pub byte_size: u64, + pub align: IgnoreInEqOrdHash, + pub ty: Vector, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PackedVectorLayout { + pub byte_size: u64, + pub align: IgnoreInEqOrdHash, + pub ty: PackedVector, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MatrixLayout { + pub byte_size: u64, + pub align: IgnoreInEqOrdHash, + pub ty: Matrix, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ArrayLayout { + pub byte_stride: u64, + // Rc here and not in TypeLayoutSemnatics, so that matches like + // TypeLayoutSemantics::Array(Array { len: Some(n), .. }) = layout.kind are possible. + pub element_ty: TypeLayout, + // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. + pub len: Option, +} + /// The memory layout of a type. /// /// This models only the layout, not other characteristics of the types. @@ -145,7 +179,7 @@ pub struct GpuTypeLayout { impl GpuTypeLayout { /// Get the TypeLayout and remove compile time guarantees about the TypeRepr". - pub fn layout(&self) -> TypeLayout { TypeLayout::new_layout_for(&self.ty, T::REPR) } + pub fn layout(&self) -> TypeLayout { todo!() } /// Returns the `LayoutableType` this `GpuTypeLayout` is based on. pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } } @@ -183,6 +217,7 @@ pub mod repr { Packed, } + impl Repr { /// True if `Repr::Storage` pub const fn is_storage(self) -> bool { matches!(self, Repr::Storage) } @@ -283,9 +318,9 @@ impl TypeLayout { TypeLayoutSemantics::Vector { .. } | TypeLayoutSemantics::PackedVector { .. } | TypeLayoutSemantics::Matrix { .. } => format!("{}", self), - TypeLayoutSemantics::Array(element_layout, n) => match n { - Some(n) => format!("array<{}, {n}>", element_layout.ty.short_name()), - None => format!("array<{}, runtime-sized>", element_layout.ty.short_name()), + TypeLayoutSemantics::Array(a) => match a.len { + Some(n) => format!("array<{}, {n}>", a.element_ty.short_name()), + None => format!("array<{}, runtime-sized>", a.element_ty.short_name()), }, TypeLayoutSemantics::Structure(s) => s.name.to_string(), } @@ -323,11 +358,11 @@ impl TypeLayout { }, Sem::PackedVector(c) => write!(f, "{}", c)?, Sem::Matrix(m) => write!(f, "{}", ir::SizedType::Matrix(m.columns, m.rows, m.scalar))?, - Sem::Array(t, n) => { - let stride = t.byte_stride; + Sem::Array(a) => { + let stride = a.byte_stride; write!(f, "array<")?; - t.ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(n) = n { + a.element_ty.write(&(indent.to_string() + tab), colored, f)?; + if let Some(n) = a.len { write!(f, ", {n}")?; } write!(f, "> stride={stride}")?; @@ -360,6 +395,128 @@ impl TypeLayout { }; Ok(()) } + + fn to_string_plain(&self) -> String { + use TypeLayoutSemantics::*; + + match &self.kind { + Vector(v) => v.to_string(), + PackedVector(c) => c.to_string(), + Matrix(m) => m.to_string(), + Array(a) => { + if let Some(n) = a.len { + format!("array<{}, {n}>", a.element_ty.to_string_plain()) + } else { + format!("array<{}>", a.element_ty.to_string_plain()) + } + } + Structure(s) => format!("{}", s.name), + } + } + + pub(crate) fn to_string_with_layout_information(&self) -> Result { + let mut s = String::new(); + self.write2(&mut s, true)?; + Ok(s) + } + + pub(crate) fn write2(&self, f: &mut W, include_layout_info: bool) -> std::fmt::Result { + use TypeLayoutSemantics::*; + let tab = " "; + + let struct_decl = |s: &StructLayout| format!("struct {} {{", s.name); + let mut field_decl = + |field: &FieldLayoutWithOffset| format!("{tab}{}: {},", field.name, field.ty.to_string_plain()); + + if !include_layout_info { + match &self.kind { + Vector(_) | PackedVector(_) | Matrix(_) | Array(_) => writeln!(f, "{}", self.to_string_plain())?, + Structure(s) => { + writeln!(f, "{}", struct_decl(s))?; + for field in s.fields.iter() { + writeln!(f, "{}", field_decl(field))?; + } + } + } + } else { + let align = |layout: &TypeLayout| layout.align.as_u32(); + let size = |layout: &TypeLayout| layout.byte_size.map(|s| s.to_string()).unwrap_or("None".into()); + + let indent = |f: &mut W, n: usize| -> std::fmt::Result { + for _ in 0..n { + f.write_char(' ')? + } + Ok(()) + }; + + match &self.kind { + Vector(_) | PackedVector(_) | Matrix(_) => { + let plain = self.to_string_plain(); + + // Write header + indent(f, plain.len() + 1)?; + f.write_str("align size\n")?; + + // Write type and layout info + writeln!(f, "{plain} {:5} {:4}", align(self), size(self))?; + } + Array(a) => { + let plain = self.to_string_plain(); + + // Write header + indent(f, plain.len() + 1)?; + f.write_str("align size stride\n")?; + + // Write type and layout info + writeln!(f, "{plain} {:5} {:4} {:6}", align(self), size(self), a.byte_stride)?; + } + Structure(s) => { + let has_array_field = s.fields.iter().any(|f| match f.ty.kind { + Array(_) => true, + Vector(_) | PackedVector(_) | Matrix(_) | Structure(_) => false, + }); + let layout_info = if has_array_field { + "offset align size stride" + } else { + "offset align size" + }; + + let max_line_len = 1 + s + .fields + .iter() + .map(field_decl) + .map(|s| s.len()) + .max() + .unwrap_or(0) + .max(struct_decl(s).len()); + + // Write header + indent(f, max_line_len); + writeln!(f, "{layout_info} {:6} {:5} {:4}", "", align(self), size(self))?; + + // Write struct and layout info + writeln!(f, "{}", struct_decl(s))?; + for field in s.fields.iter() { + write!( + f, + "{} {:6} {:5} {:4}", + field_decl(field), + field.rel_byte_offset, + align(&field.ty), + size(&field.ty), + )?; + match &field.ty.kind { + Array(a) => writeln!(f, " {:6}", a.byte_stride)?, + Vector(_) | PackedVector(_) | Matrix(_) | Structure(_) => writeln!(f, "")?, + } + } + } + }; + } + + + Ok(()) + } } impl TypeLayout { @@ -378,7 +535,7 @@ impl TypeLayout { store_type: ir::StoreType, ) -> Result { let t: layoutable::LayoutableType = store_type.try_into()?; - Ok(TypeLayout::new_layout_for(&t, Repr::Storage)) + Ok(t.layout()) } } diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index e986a6d..751e515 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -12,7 +12,7 @@ use super::{ AsAny, GpuType, To, ToGpuType, }; use crate::{ - any::layout::{self, Layoutable}, + any::layout::{self}, call_info, common::{ proc_macro_utils::{collect_into_array_exact, push_wrong_amount_of_args_error}, @@ -573,12 +573,11 @@ impl GpuStore for vec { fn impl_category() -> GpuStoreImplCategory { GpuStoreImplCategory::GpuType(Self::store_ty()) } } - -impl Layoutable for vec +impl GpuLayout for vec where vec: NoBools, { - fn layoutable_type() -> layout::LayoutableType { + fn layout_recipe() -> layout::LayoutableType { layout::Vector::new( T::SCALAR_TYPE .try_into() @@ -587,13 +586,6 @@ where ) .into() } -} - -impl GpuLayout for vec -where - vec: NoBools, -{ - type GpuRepr = repr::Storage; fn cpu_type_name_and_layout() -> Option, TypeLayout), super::layout_traits::ArrayElementsUnsizedError>> diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 4a245d2..3bec27f 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -33,7 +33,7 @@ use crate::{ error::InternalError, rust_types::{ len::x3, - type_layout::{self, layoutable, StructLayout}, + type_layout::{self, layoutable, StructLayout, DEFAULT_REPR}, }, }, ir::{ @@ -356,7 +356,7 @@ impl WipPushConstantsField { let sized_struct: layoutable::SizedStruct = sized_struct .try_into() .map_err(|e| InternalError::new(true, format!("{e}")))?; - let layout = TypeLayout::new_layout_for(&sized_struct.into(), Repr::Storage); + let layout = sized_struct.layout(DEFAULT_REPR); let layout = match &layout.kind { type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, _ => unreachable!("expected struct layout for type layout of struct"), diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 144ed18..baf348c 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -483,9 +483,6 @@ pub mod any { pub use type_layout::FieldLayout; pub use type_layout::ElementLayout; - // layoutable traits - pub use type_layout::layoutable::Layoutable; - // layoutable types pub use type_layout::layoutable::LayoutableType; pub use type_layout::layoutable::UnsizedStruct; diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index c6c12c9..a96cf9a 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -228,7 +228,18 @@ pub fn impl_for_struct( #last_field_type: #re::NoBools + #re::NoHandles + #re::Layoutable, #where_clause_predicates { - fn layoutable_type() -> #re::LayoutableType { + + } + }; + + let impl_gpu_layout = quote! { + impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> + where + #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::GpuLayout + #re::GpuSized,)* + #last_field_type: #re::NoBools + #re::NoHandles + #re::GpuLayout, + #where_clause_predicates + { + fn layout_recipe() -> #re::LayoutableType { let result = #re::LayoutableType::struct_from_parts( std::stringify!(#derive_struct_ident), [ @@ -240,7 +251,8 @@ pub fn impl_for_struct( ), <#field_type as #re::Layoutable>::layoutable_type() ),)* - ] + ], + #gpu_repr_shame, ); match result { @@ -253,17 +265,6 @@ pub fn impl_for_struct( Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), } } - } - }; - - let impl_gpu_layout = quote! { - impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> - where - #(#first_fields_type: #re::Layoutable + #re::GpuSized,)* - #last_field_type: #re::Layoutable, - #where_clause_predicates - { - type GpuRepr = #gpu_repr_shame; fn cpu_type_name_and_layout() -> Option, #re::TypeLayout), #re::ArrayElementsUnsizedError>> { use #re::CpuLayout as _; @@ -368,7 +369,6 @@ pub fn impl_for_struct( // we only implement `GpuLayout` and `VertexLayout`, as well as their implied traits { Ok(quote! { - #impl_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits From 49692f53e9499d72bd68b74b119b10a109a00ea2 Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 21 Jul 2025 22:22:50 +0200 Subject: [PATCH 137/182] New TypeLayoutCompatibleWith type and layout comparison rework --- shame/src/common/proc_macro_reexports.rs | 3 - shame/src/common/proc_macro_utils.rs | 39 +- shame/src/frontend/any/render_io.rs | 27 +- shame/src/frontend/rust_types/array.rs | 24 +- shame/src/frontend/rust_types/atomic.rs | 2 +- .../src/frontend/rust_types/layout_traits.rs | 103 +- shame/src/frontend/rust_types/mat.rs | 2 +- shame/src/frontend/rust_types/packed_vec.rs | 2 +- shame/src/frontend/rust_types/struct_.rs | 3 +- .../rust_types/type_layout/compatible_with.rs | 353 ++++--- .../rust_types/type_layout/construction.rs | 687 ------------- .../src/frontend/rust_types/type_layout/eq.rs | 556 ++++------ .../type_layout/layoutable/align_size.rs | 59 +- .../type_layout/layoutable/ir_compat.rs | 12 + .../rust_types/type_layout/layoutable/mod.rs | 2 +- .../type_layout/layoutable/to_layout.rs | 171 ++-- .../frontend/rust_types/type_layout/mod.rs | 949 ++++++++++-------- shame/src/frontend/rust_types/vec.rs | 1 - shame/src/ir/ir_type/layout_constraints.rs | 6 +- shame/src/ir/pipeline/wip_pipeline.rs | 10 +- shame/src/lib.rs | 12 +- shame/tests/test_layout.rs | 908 ++++++++--------- shame_derive/src/derive_layout.rs | 19 +- 23 files changed, 1600 insertions(+), 2350 deletions(-) delete mode 100644 shame/src/frontend/rust_types/type_layout/construction.rs diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index f69df9a..411605e 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -24,13 +24,10 @@ pub use crate::frontend::rust_types::reference::Ref; pub use crate::frontend::rust_types::struct_::BufferFields; pub use crate::frontend::rust_types::struct_::SizedFields; pub use crate::frontend::rust_types::type_layout::FieldLayout; -pub use crate::frontend::rust_types::type_layout::FieldLayoutWithOffset; pub use crate::frontend::rust_types::type_layout::layoutable::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; pub use crate::frontend::rust_types::type_layout::Repr; -pub use crate::frontend::rust_types::type_layout::repr; pub use crate::frontend::rust_types::type_layout::TypeLayout; -pub use crate::frontend::rust_types::type_layout::TypeLayoutSemantics; pub use crate::frontend::rust_types::type_traits::BindingArgs; pub use crate::frontend::rust_types::type_traits::GpuAligned; pub use crate::frontend::rust_types::type_traits::GpuSized; diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index b88d7a6..06035bd 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -5,7 +5,7 @@ use crate::{ any::{Any, InvalidReason}, rust_types::{ error::FrontendError, - type_layout::{FieldLayout, FieldLayoutWithOffset, StructLayout, TypeLayout, TypeLayoutSemantics}, + type_layout::{FieldLayout, StructLayout, TypeLayout}, }, }, ir::{ @@ -109,35 +109,32 @@ pub fn repr_c_struct_layout( .map(|(field, offset, size)| (field, *offset as u64, *size as u64)) .map(|(mut field, offset, size)| { let mut layout = field.layout.clone(); - layout.byte_size = new_size(field.layout.byte_size(), Some(size)); - FieldLayoutWithOffset { - field: FieldLayout { - name: field.name.into(), - ty: layout, - }, + layout.set_byte_size(new_size(field.layout.byte_size(), Some(size))); + FieldLayout { rel_byte_offset: offset, + name: field.name.into(), + ty: layout, } }) .chain(std::iter::once({ - last_field.layout.byte_size = new_size(last_field.layout.byte_size(), last_field_size); - FieldLayoutWithOffset { - field: FieldLayout { - name: last_field.name.into(), - ty: last_field.layout, - }, + last_field + .layout + .set_byte_size(new_size(last_field.layout.byte_size(), last_field_size)); + FieldLayout { rel_byte_offset: last_field_offset, + name: last_field.name.into(), + ty: last_field.layout, } })) .collect::>(); - Ok(TypeLayout::new( - total_struct_size, - struct_alignment, - TypeLayoutSemantics::Structure(Rc::new(StructLayout { - name: struct_name.into(), - fields, - })), - )) + Ok(StructLayout { + byte_size: total_struct_size, + align: struct_alignment.into(), + name: struct_name.into(), + fields, + } + .into()) } #[track_caller] diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 4ce732a..c329140 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -2,7 +2,7 @@ use std::{fmt::Display, rc::Rc}; use thiserror::Error; -use crate::any::layout::{GpuTypeLayout, Repr, TypeRepr}; +use crate::any::layout::{Repr}; use crate::frontend::any::Any; use crate::frontend::rust_types::type_layout::{layoutable, TypeLayout}; use crate::{ @@ -14,7 +14,6 @@ use crate::{ io_iter::LocationCounter, EncodingErrorKind, }, - rust_types::type_layout::TypeLayoutSemantics, }, ir::{ expr::{BuiltinShaderIn, BuiltinShaderIo, Expr, Interpolator, ShaderIo}, @@ -254,30 +253,30 @@ impl Attrib { let size = layout.byte_size()?; layoutable::array_stride(layout.align(), size, Repr::Storage) }; - use TypeLayoutSemantics as TLS; + use TypeLayout::*; - let attribs: Box<[Attrib]> = match &layout.kind { - TLS::Matrix(..) | TLS::Array(..) => return None, - TLS::Vector(v) => [Attrib { + let attribs: Box<[Attrib]> = match &layout { + Matrix(..) | Array(..) => return None, + Vector(v) => [Attrib { offset: 0, location: location_counter.next(), - format: VertexAttribFormat::Fine(v.len, v.scalar), + format: VertexAttribFormat::Fine(v.ty.len, v.ty.scalar), }] .into(), - TLS::PackedVector(packed_vector) => [Attrib { + PackedVector(v) => [Attrib { offset: 0, location: location_counter.next(), - format: VertexAttribFormat::Coarse(*packed_vector), + format: VertexAttribFormat::Coarse(v.ty), }] .into(), - TLS::Structure(rc) => try_collect(rc.fields.iter().map(|f| { + Struct(rc) => try_collect(rc.fields.iter().map(|f| { Some(Attrib { offset: f.rel_byte_offset, location: location_counter.next(), - format: match f.field.ty.kind { - TLS::Vector(v) => Some(VertexAttribFormat::Fine(v.len, v.scalar)), - TLS::PackedVector(packed_vector) => Some(VertexAttribFormat::Coarse(packed_vector)), - TLS::Matrix(..) | TLS::Array(..) | TLS::Structure(..) => None, + format: match &f.ty { + Vector(v) => Some(VertexAttribFormat::Fine(v.ty.len, v.ty.scalar)), + PackedVector(v) => Some(VertexAttribFormat::Coarse(v.ty)), + Matrix(..) | Array(..) | Struct(..) => None, }?, }) }))?, diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index e5101a3..4ee1156 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,7 +5,7 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{self, layoutable, repr, ElementLayout, TypeLayout, TypeLayoutSemantics}; +use super::type_layout::{self, layoutable, TypeLayout, ArrayLayout}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; @@ -20,7 +20,6 @@ use crate::frontend::encoding::buffer::{Buffer, BufferAddressSpace, BufferInner, use crate::frontend::encoding::flow::{for_range_impl, FlowFn}; use crate::frontend::error::InternalError; use crate::frontend::rust_types::reference::Ref; -use crate::frontend::rust_types::type_layout::ArrayLayout; use crate::frontend::rust_types::vec::vec; use crate::ir::ir_type::stride_of_array_from_element_align_size; use crate::ir::pipeline::StageMask; @@ -189,17 +188,16 @@ impl GpuLayout for Array { let result = ( name.into(), - TypeLayout::new( - N::LEN.map(|n| n.get() as u64 * t_cpu_size), - t_cpu_layout.align(), - TypeLayoutSemantics::Array(Rc::new(ArrayLayout { - // array stride is element size according to - // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.size - byte_stride: t_cpu_size, - element_ty: t_cpu_layout, - len: N::LEN.map(NonZeroU32::get), - })), - ), + ArrayLayout { + byte_size: N::LEN.map(|n| n.get() as u64 * t_cpu_size), + align: t_cpu_layout.align().into(), + // array stride is element size according to + // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.size + byte_stride: t_cpu_size, + element_ty: t_cpu_layout, + len: N::LEN.map(NonZeroU32::get), + } + .into(), ); Some(Ok(result)) diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index 79a5d89..3b6e8f8 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -9,7 +9,7 @@ use super::{ type_layout::{ self, layoutable::{self}, - repr, TypeLayout, + TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 3588e57..23a5429 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -10,7 +10,8 @@ use crate::frontend::encoding::buffer::{BufferAddressSpace, BufferInner, BufferR use crate::frontend::encoding::{EncodingError, EncodingErrorKind}; use crate::frontend::error::InternalError; use crate::frontend::rust_types::len::*; -use crate::frontend::rust_types::type_layout::ArrayLayout; +use crate::frontend::rust_types::type_layout::layoutable::ScalarType; +use crate::frontend::rust_types::type_layout::{ArrayLayout, VectorLayout}; use crate::ir::ir_type::{ align_of_array, align_of_array_from_element_alignment, byte_size_of_array_from_stride_len, round_up, stride_of_array_from_element_align_size, CanonName, LayoutError, ScalarTypeFp, ScalarTypeInteger, @@ -23,16 +24,12 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::repr::{TypeRepr, DerivableRepr}; use super::type_layout::layoutable::{self, array_stride, Vector}; -use super::type_layout::{ - self, repr, ElementLayout, FieldLayout, FieldLayoutWithOffset, GpuTypeLayout, StructLayout, TypeLayout, - TypeLayoutSemantics, DEFAULT_REPR, -}; +use super::type_layout::{self, FieldLayout, StructLayout, TypeLayout, DEFAULT_REPR}; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, }; -use super::{len::Len, scalar_type::ScalarType, vec::vec}; +use super::{len::Len, vec::vec}; use super::{AsAny, GpuType, ToGpuType}; use crate::frontend::any::{shared_io::BindPath, shared_io::BindingType}; use crate::frontend::rust_types::reference::Ref; @@ -707,43 +704,39 @@ where //fn gpu_type_layout() -> Option> { Some(Ok(GpuT::gpu_layout())) } } -impl CpuLayout for f32 { - fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - layoutable::ScalarType::F32, - layoutable::Len::X1, - ))) +fn cpu_layout_of_scalar(scalar: ScalarType) -> TypeLayout { + let (size, align) = match scalar { + ScalarType::F32 => (size_of::(), align_of::()), + ScalarType::F64 => (size_of::(), align_of::()), + ScalarType::U32 => (size_of::(), align_of::()), + ScalarType::I32 => (size_of::(), align_of::()), + // Waiting for f16 to become stable + // ScalarType::F16 => (size_of::(), align_of::()), + ScalarType::F16 => (2, 2), + }; + VectorLayout { + byte_size: size as u64, + // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.align + align: U32PowerOf2::try_from(align as u32) + .expect("aligns are power of 2s in rust") + .into(), + ty: Vector::new(scalar, layoutable::Len::X1), + debug_is_atomic: false, } - // fn gpu_type_layout() -> Option> { - // Some(Ok(vec::::gpu_layout())) - // } + .into() } +impl CpuLayout for f32 { + fn cpu_layout() -> TypeLayout { cpu_layout_of_scalar(ScalarType::F32) } +} impl CpuLayout for f64 { - fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - layoutable::ScalarType::F64, - layoutable::Len::X1, - ))) - } + fn cpu_layout() -> TypeLayout { cpu_layout_of_scalar(ScalarType::F64) } } - impl CpuLayout for u32 { - fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - layoutable::ScalarType::U32, - layoutable::Len::X1, - ))) - } + fn cpu_layout() -> TypeLayout { cpu_layout_of_scalar(ScalarType::U32) } } - impl CpuLayout for i32 { - fn cpu_layout() -> TypeLayout { - TypeLayout::from_rust_sized::(TypeLayoutSemantics::Vector(Vector::new( - layoutable::ScalarType::I32, - layoutable::Len::X1, - ))) - } + fn cpu_layout() -> TypeLayout { cpu_layout_of_scalar(ScalarType::I32) } } /// (no documentation yet) @@ -775,17 +768,14 @@ impl CpuAligned for [T] { impl CpuLayout for [T; N] { fn cpu_layout() -> TypeLayout { - let align = ::alignment(); - - TypeLayout::new( - Some(std::mem::size_of::() as u64), - align, - TypeLayoutSemantics::Array(Rc::new(ArrayLayout { - byte_stride: std::mem::size_of::() as u64, - element_ty: T::cpu_layout(), - len: Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), - })), - ) + ArrayLayout { + byte_size: Some(std::mem::size_of::() as u64), + align: ::alignment().into(), + byte_stride: std::mem::size_of::() as u64, + element_ty: T::cpu_layout(), + len: Some(u32::try_from(N).expect("arrays larger than u32::MAX elements are not supported by WGSL")), + } + .into() } // fn gpu_type_layout() -> Option> { @@ -816,17 +806,14 @@ impl CpuLayout for [T; N] { impl CpuLayout for [T] { fn cpu_layout() -> TypeLayout { - let align = ::alignment(); - - TypeLayout::new( - None, - align, - TypeLayoutSemantics::Array(Rc::new(ArrayLayout { - byte_stride: std::mem::size_of::() as u64, - element_ty: T::cpu_layout(), - len: None, - })), - ) + ArrayLayout { + byte_size: None, + align: ::alignment().into(), + byte_stride: std::mem::size_of::() as u64, + element_ty: T::cpu_layout(), + len: None, + } + .into() } // TODO(release) remove if we decide to not support this function on the `CpuLayout` trait diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index f672ebc..bd695fe 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -10,7 +10,7 @@ use super::{ type_layout::{ self, layoutable::{self}, - repr, TypeLayout, + TypeLayout, }, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index f79f0f2..0d788a8 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -22,7 +22,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{self, layoutable, repr, Repr, TypeLayout, TypeLayoutSemantics, DEFAULT_REPR}, + type_layout::{self, layoutable, Repr, TypeLayout, DEFAULT_REPR}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index 9c8d92d..04bd4bb 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -22,14 +22,13 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::{self, layoutable, repr, TypeLayout}; +use super::type_layout::{self, layoutable, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, layout_traits::{ArrayElementsUnsizedError, FromAnys}, mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, - type_layout::TypeLayoutSemantics, type_traits::{BindingArgs, NoAtomics, NoHandles}, typecheck_downcast, AsAny, }; diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 1855ae2..17a4fd4 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -1,19 +1,55 @@ use std::fmt::Write; use crate::{ - any::layout::{ElementLayout, StructLayout}, + any::layout::StructLayout, common::prettify::set_color, - frontend::rust_types::type_layout::ArrayLayout, + frontend::rust_types::type_layout::{align_to_string, byte_size_to_string, ArrayLayout}, + ir::ir_type::max_u64_po2_dividing, TypeLayout, }; -use super::{layoutable::LayoutableType, Repr, DEFAULT_REPR, TypeLayoutSemantics}; +use super::{layoutable::LayoutableType, Repr, DEFAULT_REPR}; pub struct TypeLayoutCompatibleWith { recipe: LayoutableType, _phantom: std::marker::PhantomData, } +impl TypeLayoutCompatibleWith { + pub fn try_from(recipe: LayoutableType) -> Result { + let address_space = AS::ADDRESS_SPACE; + let layout = recipe.layout(); + + match (address_space, layout.byte_size()) { + // Must be sized in wgsl's uniform address space + (AddressSpaceEnum::WgslUniform, None) => return Err(AddressSpaceError::MustBeSized(recipe, address_space)), + (AddressSpaceEnum::WgslUniform, Some(_)) | (AddressSpaceEnum::WgslStorage, _) => {} + } + + // Check for layout errors + let recipe_unified = recipe.to_unified_repr(address_space.matching_repr()); + let layout_unified = recipe_unified.layout(); + if layout != layout_unified { + match try_find_mismatch(&layout, &layout_unified) { + Some(mismatch) => { + return Err(LayoutError { + recipe, + address_space, + mismatch, + } + .into()); + } + None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), + } + } + + Ok(Self { + recipe, + _phantom: std::marker::PhantomData, + }) + } +} + #[derive(Debug, Clone, Copy)] pub enum AddressSpaceEnum { WgslStorage, @@ -76,38 +112,38 @@ pub struct LayoutError { impl std::error::Error for LayoutError {} impl std::fmt::Display for LayoutError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use TypeLayoutSemantics::*; + use TypeLayout::*; + + let colored = todo!(); - let types_are_the_same = "The LayoutError is produced by comparing two semantically equivalent TypeLayout, so all (nested) types are the same"; + let types_are_the_same = "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same"; match &self.mismatch { LayoutMismatch::TopLevel { - layout1, - layout2, + layout_left: layout1, + layout_right: layout2, mismatch, } => match mismatch { TopLevelMismatch::Type => unreachable!("{}", types_are_the_same), - TopLevelMismatch::ArrayStride => { - let (element1, element2) = match (&layout1.kind, &layout2.kind) { - (Array(e1, _), Array(e2, _)) => (e1, e2), - _ => unreachable!("Array stride error can only occur if the layouts are arrays"), - }; - + TopLevelMismatch::ArrayStride { + array_left, + array_right, + } => { writeln!( f, "`{}` requires a stride of {} in {}, but has a stride of {}.", - layout1.to_string_plain(), - element2.byte_stride, + array_left.short_name(), + array_right.byte_stride, self.address_space, - element1.byte_stride + array_left.byte_stride )?; } }, LayoutMismatch::Struct { - layout1, - layout2, + struct_left, + struct_right, mismatch, } => { - let field_index = match mismatch { + match mismatch { StructMismatch::FieldArrayStride { field_index, array_left, @@ -115,11 +151,88 @@ impl std::fmt::Display for LayoutError { } => { writeln!( f, - "`{}` in {} requires a stride of {} in {}, but has a stride of {}.", - *layout1.name, array_left.byte_stride, self.address_space, array_right.byte_stride + "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + struct_left.short_name(), + array_right.byte_stride, + self.address_space, + array_left.byte_stride + )?; + writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; + write_struct(f, struct_left, Some(*field_index), colored)?; + } + StructMismatch::FieldByteSize { field_index } => { + let field_left = struct_left.fields[*field_index]; + let field_right = struct_right.fields[*field_index]; + + writeln!( + f, + "Field `{}` in `{}` requires a byte size of {} in {}, but has a byte size of {}", + field_left.name, + struct_left.name, + byte_size_to_string(field_right.ty.byte_size()), + self.address_space, + byte_size_to_string(field_left.ty.byte_size()) + )?; + writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; + write_struct(f, struct_left, Some(*field_index), colored)?; + } + StructMismatch::FieldOffset { field_index } => { + let field_left = struct_left.fields[*field_index]; + let field_right = struct_right.fields[*field_index]; + let field_name = &field_left.name; + let offset = field_left.rel_byte_offset; + let expected_align = field_right.ty.align().as_u64(); + let actual_align = max_u64_po2_dividing(field_left.rel_byte_offset); + + writeln!( + f, + "Field `{}` in `{}` needs to be {} byte aligned in {}, but has a byte-offset of {} which is only {} byte aligned", + field_name, struct_left.name, expected_align, self.address_space, offset, actual_align + )?; + writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; + write_struct(f, struct_left, Some(*field_index), colored)?; + + writeln!(f, "Potential solutions include:")?; + + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}`", + align_to_string(field_right.ty.align()), + field_name + )?; + writeln!( + f, + "- increase the offset of `{field_name}` until it is divisible by {expected_align} by making previous fields larger or adding fields before it" + )?; + writeln!( + f, + "- if you are using the uniform address space, use the storage address space instead" )?; + writeln!(f)?; + + + match self.address_space { + AddressSpaceEnum::WgslUniform => writeln!( + f, + "In the {}, structs, arrays and array elements must be at least 16 byte aligned.", + self.address_space + )?, + AddressSpaceEnum::WgslStorage => {} + } + + match self.address_space { + (AddressSpaceEnum::WgslUniform | AddressSpaceEnum::WgslStorage) => writeln!( + f, + "More info about the {} layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + self.address_space + )?, + } } - StructMismatch::FieldCount => unreachable!("{}", types_are_the_same), + StructMismatch::FieldCount | StructMismatch::FieldType { .. } => { + unreachable!("{}", types_are_the_same) + } + _ => todo!(), }; } } @@ -127,39 +240,32 @@ impl std::fmt::Display for LayoutError { } } -impl TypeLayoutCompatibleWith { - pub fn try_from(recipe: LayoutableType) -> Result { - let address_space = AS::ADDRESS_SPACE; - let layout = recipe.layout(); - - match (address_space, layout.byte_size()) { - // Must be sized in wgsl's uniform address space - (AddressSpaceEnum::WgslUniform, None) => return Err(AddressSpaceError::MustBeSized(recipe, address_space)), - (AddressSpaceEnum::WgslUniform, Some(_)) | (AddressSpaceEnum::WgslStorage, _) => {} - } - - // Check for layout errors - let recipe_unified = recipe.to_unified_repr(address_space.matching_repr()); - let layout_unified = recipe_unified.layout(); - if layout != layout_unified { - match try_find_mismatch(&layout, &layout_unified) { - Some(mismatch) => { - return Err(LayoutError { - recipe, - address_space, - mismatch, - } - .into()); - } - None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), +fn write_struct(f: &mut W, s: &StructLayout, highlight_field: Option, colored: bool) -> std::fmt::Result +where + W: Write, +{ + let use_256_color_mode = false; + + let mut writer = s.writer(true); + writer.writeln_header(f); + writer.writeln_struct_declaration(f); + for field_index in 0..s.fields.len() { + if Some(field_index) == highlight_field { + if colored { + set_color(f, Some("#508EE3"), use_256_color_mode)?; + } + writer.write_field(f, field_index)?; + writeln!(f, " <--")?; + if colored { + set_color(f, None, use_256_color_mode)?; } + } else { + writer.writeln_field(f, field_index); } - - Ok(Self { - recipe, - _phantom: std::marker::PhantomData, - }) } + writer.writeln_struct_end(f)?; + + Ok(()) } #[derive(Debug, Clone)] @@ -170,13 +276,13 @@ pub enum LayoutMismatch { /// of `Array>` even if the array stride mismatch /// may be happening for the inner `Array`. TopLevel { - layout1: TypeLayout, - layout2: TypeLayout, + layout_left: TypeLayout, + layout_right: TypeLayout, mismatch: TopLevelMismatch, }, Struct { - layout1: StructLayout, - layout2: StructLayout, + struct_left: StructLayout, + struct_right: StructLayout, mismatch: StructMismatch, }, } @@ -213,16 +319,17 @@ pub enum StructMismatch { }, } -fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { - use TypeLayoutSemantics::*; +/// Find the first depth first layout mismatch +pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { + use TypeLayout::*; // First check if the kinds are the same type - match (&layout1.kind, &layout2.kind) { + match (&layout1, &layout2) { (Vector(v1), Vector(v2)) => { if v1 != v2 { return Some(LayoutMismatch::TopLevel { - layout1: layout1.clone(), - layout2: layout2.clone(), + layout_left: layout1.clone(), + layout_right: layout2.clone(), mismatch: TopLevelMismatch::Type, }); } @@ -230,8 +337,8 @@ fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { if p1 != p2 { return Some(LayoutMismatch::TopLevel { - layout1: layout1.clone(), - layout2: layout2.clone(), + layout_left: layout1.clone(), + layout_right: layout2.clone(), mismatch: TopLevelMismatch::Type, }); } @@ -239,8 +346,8 @@ fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { if m1 != m2 { return Some(LayoutMismatch::TopLevel { - layout1: layout1.clone(), - layout2: layout2.clone(), + layout_left: layout1.clone(), + layout_right: layout2.clone(), mismatch: TopLevelMismatch::Type, }); } @@ -249,8 +356,8 @@ fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option Option Option vs Array mismatch // Note that we don't change the TopLevelMismatch::ArrayStride fields though. Some(LayoutMismatch::TopLevel { - layout1, - layout2, + layout_left: layout1, + layout_right: layout2, mismatch, }) => match mismatch { TopLevelMismatch::Type | TopLevelMismatch::ArrayStride { .. } => { return Some(LayoutMismatch::TopLevel { - layout1: layout1.clone(), - layout2: layout2.clone(), + layout_left: layout1.clone(), + layout_right: layout2.clone(), mismatch, }); } @@ -292,14 +399,14 @@ fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option return None, } } - (Structure(s1), Structure(s2)) => { + (Struct(s1), Struct(s2)) => { return try_find_struct_mismatch(s1, s2); } // Different kinds entirely. Matching exhaustively, so that changes to TypeLayout lead us here. - (Vector(_) | PackedVector(_) | Matrix(_) | Array(_) | Structure(_), _) => { + (Vector(_) | PackedVector(_) | Matrix(_) | Array(_) | Struct(_), _) => { return Some(LayoutMismatch::TopLevel { - layout1: layout1.clone(), - layout2: layout2.clone(), + layout_left: layout1.clone(), + layout_right: layout2.clone(), mismatch: TopLevelMismatch::Type, }); } @@ -312,8 +419,8 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O // Check field count if struct1.fields.len() != struct2.fields.len() { return Some(LayoutMismatch::Struct { - layout1: struct1.clone(), - layout2: struct2.clone(), + struct_left: struct1.clone(), + struct_right: struct2.clone(), mismatch: StructMismatch::FieldCount, }); } @@ -322,23 +429,23 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O // Check field offset if field1.rel_byte_offset != field2.rel_byte_offset { return Some(LayoutMismatch::Struct { - layout1: struct1.clone(), - layout2: struct2.clone(), + struct_left: struct1.clone(), + struct_right: struct2.clone(), mismatch: StructMismatch::FieldOffset { field_index }, }); } // Check field byte size - if field1.field.ty.byte_size != field2.field.ty.byte_size { + if field1.ty.byte_size() != field2.ty.byte_size() { return Some(LayoutMismatch::Struct { - layout1: struct1.clone(), - layout2: struct2.clone(), + struct_left: struct1.clone(), + struct_right: struct2.clone(), mismatch: StructMismatch::FieldByteSize { field_index }, }); } // Recursively check field types - if let Some(inner_mismatch) = try_find_mismatch(&field1.field.ty, &field2.field.ty) { + if let Some(inner_mismatch) = try_find_mismatch(&field1.ty, &field2.ty) { match inner_mismatch { // If it's a top-level mismatch, convert it to a field mismatch LayoutMismatch::TopLevel { @@ -346,8 +453,8 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O .. } => { return Some(LayoutMismatch::Struct { - layout1: struct1.clone(), - layout2: struct2.clone(), + struct_left: struct1.clone(), + struct_right: struct2.clone(), mismatch: StructMismatch::FieldType { field_index }, }); } @@ -360,8 +467,8 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O .. } => { return Some(LayoutMismatch::Struct { - layout1: struct1.clone(), - layout2: struct2.clone(), + struct_left: struct1.clone(), + struct_right: struct2.clone(), mismatch: StructMismatch::FieldArrayStride { field_index, array_left, @@ -377,71 +484,3 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O None } - -#[derive(Debug, Clone, PartialEq)] -pub enum DiffLine { - Shared(String), - Left(String), - Right(String), -} - -pub fn diff_lines(left: &str, right: &str) -> Vec { - let left_lines: Vec<&str> = left.lines().collect(); - let right_lines: Vec<&str> = right.lines().collect(); - - let mut result = Vec::new(); - let mut left_idx = 0; - let mut right_idx = 0; - - while left_idx < left_lines.len() && right_idx < right_lines.len() { - if left_lines[left_idx] == right_lines[right_idx] { - result.push(DiffLine::Shared(left_lines[left_idx].to_string())); - left_idx += 1; - right_idx += 1; - } else { - // Find the next matching line - let mut found_match = false; - for i in (left_idx + 1)..left_lines.len() { - if let Some(j) = right_lines[(right_idx + 1)..] - .iter() - .position(|&line| line == left_lines[i]) - { - let right_match_idx = right_idx + 1 + j; - - // Add differing lines before the match - for k in left_idx..i { - result.push(DiffLine::Left(left_lines[k].to_string())); - } - for k in right_idx..right_match_idx { - result.push(DiffLine::Right(right_lines[k].to_string())); - } - - left_idx = i; - right_idx = right_match_idx; - found_match = true; - break; - } - } - - if !found_match { - result.push(DiffLine::Left(left_lines[left_idx].to_string())); - result.push(DiffLine::Right(right_lines[right_idx].to_string())); - left_idx += 1; - right_idx += 1; - } - } - } - - // Add remaining lines - while left_idx < left_lines.len() { - result.push(DiffLine::Left(left_lines[left_idx].to_string())); - left_idx += 1; - } - - while right_idx < right_lines.len() { - result.push(DiffLine::Right(right_lines[right_idx].to_string())); - right_idx += 1; - } - - result -} diff --git a/shame/src/frontend/rust_types/type_layout/construction.rs b/shame/src/frontend/rust_types/type_layout/construction.rs deleted file mode 100644 index 2aae5b6..0000000 --- a/shame/src/frontend/rust_types/type_layout/construction.rs +++ /dev/null @@ -1,687 +0,0 @@ -use std::fmt::Formatter; - -use crate::{ - __private::SmallVec, - ir::{ir_type::max_u64_po2_dividing, StoreType}, -}; -use super::{ - layoutable::{ - FieldOffsets, MatrixMajor, RuntimeSizedArray, RuntimeSizedArrayField, SizedField, SizedStruct, SizedType, - UnsizedStruct, - }, - *, -}; -use TypeLayoutSemantics as TLS; - -impl GpuTypeLayout { - /// Creates a new `GpuTypeLayout` where `T` implements [`DerivableRepr`]. - /// - /// All LayoutableType's can be layed out according to storage and packed layout rules, - /// which corresponds to the available #[gpu_repr(packed)] and #[gpu_repr(storage)] - /// attributes when deriving `GpuLayout` for a struct. - /// - /// `GpuTypeLayout` can be acquired via - /// `GpuTypeLayout::::try_from(gpu_type_layout_storage)`. - pub fn new(ty: impl Into) -> Self { - Self { - ty: ty.into(), - _repr: PhantomData, - } - } -} - -impl TryFrom> for GpuTypeLayout { - type Error = LayoutError; - - fn try_from(ty: GpuTypeLayout) -> Result { - check_repr_equivalence_for_type(ty.layoutable_type(), Repr::Storage, Repr::Uniform)?; - Ok(GpuTypeLayout { - ty: ty.ty, - _repr: PhantomData, - }) - } -} - -/// Checks whether the layout of `ty` as `actual_repr` and as `expected_repr` are compatible. -/// Compatible means that all field offsets of structs and all strides of arrays are the same. -/// -/// Another way to say this is, that we are laying `ty` out according to two different -/// layout rules and checking whether the byte representation of those layouts is the same. -fn check_repr_equivalence_for_type( - ty: &LayoutableType, - actual_repr: Repr, - expected_repr: Repr, -) -> Result<(), LayoutError> { - let ctx = LayoutContext { - top_level_type: ty, - actual_repr, - expected_repr, - use_color: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false), - }; - let is_sized = matches!(ctx.top_level_type, LayoutableType::Sized(_)); - match (ctx.expected_repr, is_sized) { - (Repr::Uniform, false) => { - return Err(LayoutError::UniformBufferMustBeSized( - ctx.top_level_type.clone(), - Repr::Uniform, - )); - } - (Repr::Storage | Repr::Packed, _) | (Repr::Uniform, true) => {} - } - - match &ctx.top_level_type { - LayoutableType::Sized(s) => check_compare_sized(ctx, s), - LayoutableType::UnsizedStruct(s) => { - let mut actual_offsets = s.field_offsets(ctx.actual_repr); - let mut expected_offsets = s.field_offsets(ctx.expected_repr); - check_sized_fields( - ctx, - s, - s.sized_fields.iter().zip( - actual_offsets - .sized_field_offsets() - .zip(expected_offsets.sized_field_offsets()), - ), - )?; - - let (actual_last_offset, _) = actual_offsets.last_field_offset_and_struct_align(); - let (expected_last_offset, _) = expected_offsets.last_field_offset_and_struct_align(); - - if actual_last_offset != expected_last_offset { - let field = &s.last_unsized; - let field_index = s.sized_fields.len(); - return Err(StructFieldOffsetError { - ctx: ctx.into(), - struct_type: s.clone().into(), - field_name: field.name.clone(), - field_index, - actual_offset: actual_last_offset, - expected_alignment: field.align(ctx.expected_repr), - } - .into()); - } - - Ok(()) - } - LayoutableType::RuntimeSizedArray(a) => { - let actual_stride = a.byte_stride(ctx.actual_repr); - let expected_stride = a.byte_stride(ctx.expected_repr); - match actual_stride == expected_stride { - false => Err(ArrayStrideError { - ctx: ctx.into(), - actual_stride, - expected_stride, - element_ty: a.element.clone(), - } - .into()), - true => Ok(()), - } - } - } -} - -fn check_compare_sized(ctx: LayoutContext, ty: &SizedType) -> Result<(), LayoutError> { - match ty { - SizedType::Struct(s) => { - let mut actual_offsets = s.field_offsets(ctx.actual_repr); - let mut expected_offsets = s.field_offsets(ctx.expected_repr); - check_sized_fields( - ctx, - s, - s.fields().iter().zip((&mut actual_offsets).zip(&mut expected_offsets)), - ) - } - SizedType::Array(a) => { - let actual_stride = a.byte_stride(ctx.actual_repr); - let expected_stride = a.byte_stride(ctx.expected_repr); - match actual_stride == expected_stride { - false => Err(ArrayStrideError { - ctx: ctx.into(), - actual_stride, - expected_stride, - element_ty: (*a.element).clone(), - } - .into()), - true => Ok(()), - } - } - SizedType::Vector(_) | SizedType::Matrix(_) | SizedType::Atomic(_) | SizedType::PackedVec(_) => { - assert_eq!(ty.byte_size(ctx.actual_repr), ty.byte_size(ctx.expected_repr)); - Ok(()) - } - } -} - -fn check_sized_fields<'a>( - ctx: LayoutContext, - s: &(impl Into + Clone), - fields_actual_and_expected_offsets: impl Iterator, -) -> Result<(), LayoutError> { - for (i, (field, (actual_offset, expected_offset))) in fields_actual_and_expected_offsets.enumerate() { - if actual_offset != expected_offset { - return Err(StructFieldOffsetError { - ctx: ctx.into(), - struct_type: s.clone().into(), - field_name: field.name.clone(), - field_index: i, - actual_offset, - expected_alignment: field.align(ctx.expected_repr), - } - .into()); - } - - check_compare_sized(ctx, &field.ty)?; - } - - Ok(()) -} - -/// Enum of possible errors during comparison of two layouts for the same `LayoutableType`. -#[derive(thiserror::Error, Debug, Clone)] -pub enum LayoutError { - #[error("{0}")] - ArrayStride(#[from] ArrayStrideError), - #[error("{0}")] - StructureFieldOffset(#[from] StructFieldOffsetError), - #[error( - "The size of `{1}` on the gpu is not known at compile time. `{0}` \ - requires that the size of uniform buffers on the gpu is known at compile time." - )] - UniformBufferMustBeSized(LayoutableType, Repr), - #[error("{0} contains a `PackedVector`, which are not allowed in {1} memory layouts ")] - MayNotContainPackedVec(LayoutableType, Repr), -} - -#[derive(Debug, Clone, Copy)] -pub struct LayoutContext<'a> { - top_level_type: &'a LayoutableType, - actual_repr: Repr, - expected_repr: Repr, - use_color: bool, -} - -#[derive(Debug, Clone)] -pub struct LayoutErrorContext { - top_level_type: LayoutableType, - actual_repr: Repr, - expected_repr: Repr, - use_color: bool, -} - -impl From> for LayoutErrorContext { - fn from(ctx: LayoutContext) -> Self { - LayoutErrorContext { - top_level_type: ctx.top_level_type.clone(), - actual_repr: ctx.actual_repr, - expected_repr: ctx.expected_repr, - use_color: ctx.use_color, - } - } -} - -impl Repr { - fn more_info_at(&self) -> &str { "https://www.w3.org/TR/WGSL/#memory-layouts" } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct ArrayStrideError { - ctx: LayoutErrorContext, - expected_stride: u64, - actual_stride: u64, - element_ty: SizedType, -} - -impl std::error::Error for ArrayStrideError {} -impl Display for ArrayStrideError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let top_level = &self.ctx.top_level_type; - writeln!( - f, - "array elements within type `{}` do not satisfy {} layout requirements.", - top_level, self.ctx.expected_repr, - )?; - writeln!( - f, - "The array with `{}` elements requires stride {}, but has stride {}.", - self.element_ty, self.expected_stride, self.actual_stride - )?; - writeln!(f, "The full layout of `{}` is:", top_level)?; - let layout = TypeLayout::new_layout_for(&self.ctx.top_level_type, self.ctx.actual_repr); - layout.write("", self.ctx.use_color, f)?; - writeln!(f)?; - writeln!( - f, - "\nfor more information on the layout rules, see {}", - self.ctx.expected_repr.more_info_at() - )?; - Ok(()) - } -} - -#[allow(missing_docs)] -#[derive(Debug, Clone)] -pub struct StructFieldOffsetError { - pub ctx: LayoutErrorContext, - pub struct_type: StructKind, - pub field_name: CanonName, - pub field_index: usize, - pub actual_offset: u64, - pub expected_alignment: U32PowerOf2, -} - -#[derive(Debug, Clone)] -pub enum StructKind { - Sized(SizedStruct), - Unsized(UnsizedStruct), -} - -impl From for StructKind { - fn from(value: SizedStruct) -> Self { StructKind::Sized(value) } -} -impl From for StructKind { - fn from(value: UnsizedStruct) -> Self { StructKind::Unsized(value) } -} - -impl std::error::Error for StructFieldOffsetError {} -impl Display for StructFieldOffsetError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let top_level_type = &self.ctx.top_level_type; - let top_level_name = match top_level_type { - LayoutableType::Sized(SizedType::Struct(s)) => Some(&s.name), - LayoutableType::UnsizedStruct(s) => Some(&s.name), - _ => None, - }; - - let struct_name = match &self.struct_type { - StructKind::Sized(s) => &s.name, - StructKind::Unsized(s) => &s.name, - }; - - let is_top_level = match &self.struct_type { - StructKind::Sized(s) => top_level_type == &LayoutableType::Sized(SizedType::Struct(s.clone())), - StructKind::Unsized(s) => top_level_type == &LayoutableType::UnsizedStruct(s.clone()), - }; - - let structure_def_location = Context::try_with(call_info!(), |ctx| -> Option<_> { - match &self.struct_type { - StructKind::Sized(s) => { - let s: ir::SizedStruct = s.clone().try_into().ok()?; - ctx.struct_registry().get(&s).map(|def| def.call_info()) - } - StructKind::Unsized(s) => { - let s: ir::BufferBlock = s.clone().try_into().ok()?; - ctx.struct_registry().get(&s).map(|def| def.call_info()) - } - } - }) - .flatten(); - - writeln!( - f, - "The type `{top_level_type}` cannot be layed out according to {} layout rules.", - self.ctx.expected_repr - )?; - match is_top_level { - true => write!(f, "Struct `{}`", struct_name)?, - false => write!(f, "It contains a struct `{}`, which", struct_name)?, - } - writeln!( - f, - " does not satisfy the {} memory layout requirements.", - self.ctx.expected_repr - )?; - writeln!(f)?; - - if let Some(call_info) = structure_def_location { - writeln!(f, "Definition at {call_info}")?; - } - - write_struct_layout( - &self.struct_type, - self.ctx.actual_repr, - self.ctx.use_color, - Some(self.field_index), - f, - )?; - - let actual_align = max_u64_po2_dividing(self.actual_offset); - let expected_alignment = self.expected_alignment.as_u32(); - writeln!(f)?; - set_color(f, Some("#508EE3"), false)?; - writeln!( - f, - "Field `{}` needs to be {} byte aligned, but has a byte-offset of {} which is only {actual_align} byte aligned.", - self.field_name, expected_alignment, self.actual_offset - )?; - set_color(f, None, false)?; - writeln!(f)?; - - writeln!(f, "Potential solutions include:")?; - writeln!( - f, - "- add an #[align({})] attribute to the definition of `{}` (not supported by OpenGL/GLSL pipelines)", - expected_alignment, self.field_name - )?; - #[allow(clippy::single_match)] - match (self.ctx.actual_repr, self.ctx.expected_repr) { - (Repr::Storage, Repr::Uniform) => writeln!( - f, - "- use the storage address space instead of the uniform address space" - )?, - _ => {} - } - - writeln!( - f, - "- increase the offset of `{}` until it is divisible by {} by making previous fields larger or adding fields before it", - self.field_name, expected_alignment - )?; - writeln!(f)?; - - match self.ctx.expected_repr { - Repr::Uniform => writeln!( - f, - "In the {} address space, structs, arrays and array elements must be at least 16 byte aligned.", - self.ctx.expected_repr - )?, - Repr::Packed | Repr::Storage => {} - } - - match self.ctx.expected_repr { - r @ (Repr::Storage | Repr::Uniform) => writeln!( - f, - "More info about the {} address space layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - r - )?, - Repr::Packed => {} - } - - Ok(()) - } -} - -fn write_struct_layout( - struct_type: &StructKind, - repr: Repr, - colored: bool, - highlight_field: Option, - f: &mut F, -) -> std::fmt::Result -where - F: Write, -{ - let use_256_color_mode = false; - let color = |f_: &mut F, hex, field_index| match colored && Some(field_index) == highlight_field { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut F, field_index| match colored && Some(field_index) == highlight_field { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - let (struct_name, sized_fields, mut field_offsets) = match struct_type { - StructKind::Sized(s) => (&s.name, s.fields(), s.field_offsets(repr).into_inner()), - StructKind::Unsized(s) => (&s.name, s.sized_fields.as_slice(), s.field_offsets(repr).into_inner()), - }; - - let indent = " "; - let field_decl_line = |field: &SizedField| format!("{indent}{}: {},", field.name, field.ty); - let header = format!("struct {} {{", struct_name); - let table_start_column = 1 + sized_fields - .iter() - .map(field_decl_line) - .map(|s| s.len()) - .max() - .unwrap_or(0) - .max(header.chars().count()); - f.write_str(&header)?; - for i in header.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "offset align size")?; - for (field_index, (field, field_offset)) in sized_fields.iter().zip(&mut field_offsets).enumerate() { - color(f, "#508EE3", field_index)?; - - let (align, size) = (field.align(repr), field.byte_size(repr)); - let decl_line = field_decl_line(field); - f.write_str(&decl_line)?; - // write spaces to table on the right - for _ in decl_line.len()..table_start_column { - f.write_char(' ')? - } - writeln!(f, "{:6} {:5} {:4}", field_offset, align.as_u32(), size)?; - - reset(f, field_index)?; - } - if let StructKind::Unsized(s) = struct_type { - let field_index = sized_fields.len(); - color(f, "#508EE3", field_index)?; - - let last_field = &s.last_unsized; - let (last_field_offset, _) = s.field_offsets(repr).last_field_offset_and_struct_align(); - - let decl_line = format!("{indent}{}: {},", last_field.name, last_field.array); - f.write_str(&decl_line)?; - // write spaces to table on the right - for _ in decl_line.len()..table_start_column { - f.write_char(' ')? - } - write!(f, "{:6} {:5}", last_field_offset, last_field.align(repr).as_u64())?; - - reset(f, field_index)?; - } - writeln!(f, "}}")?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - any::U32PowerOf2, - frontend::rust_types::type_layout::{ - layoutable::{*}, - repr::Repr, - *, - }, - }; - use std::{rc::Rc, num::NonZeroU32}; - - #[test] - fn test_array_alignment() { - let array = SizedArray::new( - Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), - NonZeroU32::new(1).unwrap(), - ) - .into(); - - let storage = TypeLayout::new_layout_for(&array, Repr::Storage); - let uniform = TypeLayout::new_layout_for(&array, Repr::Uniform); - let packed = TypeLayout::new_layout_for(&array, Repr::Packed); - - assert_eq!(storage.align(), U32PowerOf2::_4); - assert_eq!(uniform.align(), U32PowerOf2::_16); - assert_eq!(packed.align(), U32PowerOf2::_1); - - assert_eq!(storage.byte_size(), Some(4)); - assert_eq!(uniform.byte_size(), Some(16)); - assert_eq!(packed.byte_size(), Some(4)); - - match (storage.kind, uniform.kind, packed.kind) { - ( - TypeLayoutSemantics::Array(storage, Some(1)), - TypeLayoutSemantics::Array(uniform, Some(1)), - TypeLayoutSemantics::Array(packed, Some(1)), - ) => { - assert_eq!(storage.byte_stride, 4); - assert_eq!(uniform.byte_stride, 16); - assert_eq!(packed.byte_stride, 4); - } - _ => panic!("Unexpected layout kind"), - } - } - - #[test] - fn test_struct_alignment() { - let s = SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1)).into(); - - let storage = TypeLayout::new_layout_for(&s, Repr::Storage); - let uniform = TypeLayout::new_layout_for(&s, Repr::Uniform); - let packed = TypeLayout::new_layout_for(&s, Repr::Packed); - - assert_eq!(storage.align(), U32PowerOf2::_4); - assert_eq!(uniform.align(), U32PowerOf2::_16); - assert_eq!(packed.align(), U32PowerOf2::_1); - - assert_eq!(storage.byte_size(), Some(4)); - assert_eq!(uniform.byte_size(), Some(16)); - assert_eq!(packed.byte_size(), Some(4)); - } - - #[test] - fn test_nested_struct_field_offset() { - let s = SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1)); - let s = SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1)) - .extend("b", s) // offset 4 for storage and packed, offset 16 for uniform - .into(); - - let storage = TypeLayout::new_layout_for(&s, Repr::Storage); - let uniform = TypeLayout::new_layout_for(&s, Repr::Uniform); - let packed = TypeLayout::new_layout_for(&s, Repr::Packed); - - assert_eq!(storage.align(), U32PowerOf2::_4); - assert_eq!(uniform.align(), U32PowerOf2::_16); - assert_eq!(packed.align(), U32PowerOf2::_1); - - assert_eq!(storage.byte_size(), Some(8)); - // field b is bytes 16..=19 and struct size must be a multiple of the struct align (16) - assert_eq!(uniform.byte_size(), Some(32)); - assert_eq!(packed.byte_size(), Some(8)); - - match (storage.kind, uniform.kind, packed.kind) { - ( - TypeLayoutSemantics::Structure(storage), - TypeLayoutSemantics::Structure(uniform), - TypeLayoutSemantics::Structure(packed), - ) => { - assert_eq!(storage.fields[1].rel_byte_offset, 4); - assert_eq!(uniform.fields[1].rel_byte_offset, 16); - assert_eq!(packed.fields[1].rel_byte_offset, 4); - } - _ => panic!("Unexpected layout kind"), - } - } - - #[test] - fn test_array_in_struct_field_offset() { - let s = SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1)) - .extend( - "b", - SizedArray::new( - Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), - NonZeroU32::new(1).unwrap(), - ), - ) // offset 4 for storage and packed, offset 16 for uniform - .into(); - - let storage = TypeLayout::new_layout_for(&s, Repr::Storage); - let uniform = TypeLayout::new_layout_for(&s, Repr::Uniform); - let packed = TypeLayout::new_layout_for(&s, Repr::Packed); - - assert_eq!(storage.align(), U32PowerOf2::_4); - assert_eq!(uniform.align(), U32PowerOf2::_16); - assert_eq!(packed.align(), U32PowerOf2::_1); - - assert_eq!(storage.byte_size(), Some(8)); - // field b is bytes 16..=19 and struct size must be a multiple of the struct align (16) - assert_eq!(uniform.byte_size(), Some(32)); - assert_eq!(packed.byte_size(), Some(8)); - - match (storage.kind, uniform.kind, packed.kind) { - ( - TypeLayoutSemantics::Structure(storage), - TypeLayoutSemantics::Structure(uniform), - TypeLayoutSemantics::Structure(packed), - ) => { - assert_eq!(storage.fields[1].rel_byte_offset, 4); - assert_eq!(uniform.fields[1].rel_byte_offset, 16); - assert_eq!(packed.fields[1].rel_byte_offset, 4); - } - _ => panic!("Unexpected layout kind"), - } - } - - #[test] - fn test_unsized_struct_layout() { - let unsized_struct = UnsizedStruct { - name: CanonName::from("TestStruct"), - sized_fields: vec![ - SizedField { - name: CanonName::from("field1"), - custom_min_size: None, - custom_min_align: None, - ty: Vector::new(ScalarType::F32, Len::X2).into(), - }, - SizedField { - name: CanonName::from("field2"), - custom_min_size: None, - custom_min_align: None, - ty: Vector::new(ScalarType::F32, Len::X1).into(), - }, - ], - last_unsized: RuntimeSizedArrayField { - name: CanonName::from("dynamic_array"), - custom_min_align: None, - array: RuntimeSizedArray { - element: Vector::new(ScalarType::F32, Len::X1).into(), - }, - }, - } - .into(); - - let layout = TypeLayout::new_layout_for(&unsized_struct, Repr::Storage); - assert_eq!(layout.byte_size(), None); - assert!(layout.align().as_u64() == 8); // align of vec2 - match &layout.kind { - TypeLayoutSemantics::Structure(struct_layout) => { - assert_eq!(struct_layout.fields.len(), 3); - assert_eq!(struct_layout.fields[0].field.name, CanonName::from("field1")); - assert_eq!(struct_layout.fields[1].field.name, CanonName::from("field2")); - assert_eq!(struct_layout.fields[2].field.name, CanonName::from("dynamic_array")); - - assert_eq!(struct_layout.fields[0].rel_byte_offset, 0); // vec2 - assert_eq!(struct_layout.fields[1].rel_byte_offset, 8); // f32 - assert_eq!(struct_layout.fields[2].rel_byte_offset, 12); // Array - // The last field should be an unsized array - match &struct_layout.fields[2].field.ty.kind { - TypeLayoutSemantics::Array(array, None) => assert_eq!(array.byte_stride, 4), - _ => panic!("Expected runtime-sized array for last field"), - } - } - _ => panic!("Expected structure layout"), - } - - // Testing uniform representation - let layout = TypeLayout::new_layout_for(&unsized_struct, Repr::Uniform); - assert_eq!(layout.byte_size(), None); - // Struct alignmment has to be a multiple of 16, but the runtime sized array - // also has an alignment of 16, which transfers to the struct alignment. - assert!(layout.align().as_u64() == 16); - match &layout.kind { - TypeLayoutSemantics::Structure(struct_layout) => { - assert_eq!(struct_layout.fields[0].rel_byte_offset, 0); // vec2 - assert_eq!(struct_layout.fields[1].rel_byte_offset, 8); // f32 - // array has alignment of 16, so offset should be 16 - assert_eq!(struct_layout.fields[2].rel_byte_offset, 16); // Array - match &struct_layout.fields[2].ty.kind { - // Stride has to be a multiple of 16 in uniform address space - TypeLayoutSemantics::Array(array, None) => assert_eq!(array.byte_stride, 16), - _ => panic!("Expected runtime-sized array for last field"), - } - } - _ => panic!("Expected structure layout"), - } - } -} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index ccbdebb..abe72b9 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,3 +1,6 @@ +use crate::frontend::rust_types::type_layout::compatible_with::TopLevelMismatch; + +use super::compatible_with::try_find_mismatch; use super::*; /// Error of two layouts mismatching. Implements Display for a visualization of the mismatch. @@ -10,420 +13,146 @@ pub struct LayoutMismatch { impl std::fmt::Debug for LayoutMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self) // use Display - } -} - -impl LayoutMismatch { - fn pad_width(name_a: &str, name_b: &str) -> usize { name_a.chars().count().max(name_b.chars().count()) + SEP.len() } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{self}") } } impl Display for LayoutMismatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let colored = self.colored_error; let [(a_name, a), (b_name, b)] = &self.layouts; - write!(f, "{:width$}", ' ', width = Self::pad_width(a_name, b_name))?; let layouts = [(a_name.as_str(), a), (b_name.as_str(), b)]; - match LayoutMismatch::write("", layouts, colored, f) { - Err(MismatchWasFound) => Ok(()), - Ok(KeepWriting) => { + match LayoutMismatch::write(f, layouts, self.colored_error)? { + Mismatch::Found => {} + Mismatch::NotFound => { writeln!( f, "" )?; writeln!(f, "the full type layouts in question are:")?; for (name, layout) in layouts { - writeln!(f, "`{}`:", name)?; - writeln!(f, "{}", layout)?; + writeln!(f, "`{name}`:")?; + writeln!(f, "{layout}")?; } - Ok(()) } } + + Ok(()) } } -/// layout mismatch diff name separator -/// used in the display impl of LayoutMismatch to show the actual place where the layouts mismatch -/// ``` -/// layout mismatch: -/// cpu{SEP} f32 -/// gpu{SEP} i32 -/// ``` -const SEP: &str = ": "; - -/// whether the mismatching part of the TypeLayouts in a LayoutMismatch was already expressed via writes. -/// indicates that the `write` function should stop writing. -pub(crate) struct MismatchWasFound; -pub(crate) struct KeepWriting; +/// Whether the mismatch was found or not. +pub(crate) enum Mismatch { + Found, + NotFound, +} impl LayoutMismatch { - //TODO(low prio) try to figure out a cleaner way of writing these. - - /// this function uses the `Err(MismatchWasFound)` to halt traversing the typelayout. - /// It does not constitute an error of this function, it is just so the ? operator can be used to propagate the abort. #[allow(clippy::needless_return)] pub(crate) fn write( - indent: &str, + f: &mut W, layouts: [(&str, &TypeLayout); 2], colored: bool, - f: &mut W, - ) -> Result { + ) -> Result { let tab = " "; + // TODO(chronicl) include names somehow let [(a_name, a), (b_name, b)] = layouts; - if a == b { - a.write(indent, colored, f); - return Ok(KeepWriting); - } - - let use_256_color_mode = false; - let hex_color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let color_reset = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), + // Using try_find_mismatch to find the first mismatching type / struct field. + let Some(mismatch) = try_find_mismatch(a, b) else { + return Ok(Mismatch::NotFound); }; - let color_a_hex = "#DF5853"; - let color_b_hex = "#9A639C"; - let color_a = |f| hex_color(f, color_a_hex); - let color_b = |f| hex_color(f, color_b_hex); - - let pad_width = Self::pad_width(a_name, b_name); - - use TypeLayoutSemantics as S; - match (&a.kind, &b.kind) { - (S::Structure(sa), S::Structure(sb)) => { - let max_fields = sa.all_fields().len().max(sb.all_fields().len()); - { - write!(f, "struct "); - hex_color(f, color_a_hex); - write!(f, "{}", sa.name); - color_reset(f); - write!(f, " / "); - hex_color(f, color_b_hex); - write!(f, "{}", sb.name); - color_reset(f); - writeln!(f, " {{"); - } - - let mut sa_fields = sa.all_fields().iter(); - let mut sb_fields = sb.all_fields().iter(); - - loop { - //TODO(low prio) get a hold of the code duplication here - match (sa_fields.next(), sb_fields.next()) { - (Some(a_field), Some(b_field)) => { - let offsets_match = a_field.rel_byte_offset == b_field.rel_byte_offset; - let types_match = a_field.field.ty == b_field.field.ty; - if !offsets_match && types_match { - // only write this mismatch if the types are also the same, otherwise display the detailed type mismatch further below - let a_ty_string = a_field.field.ty.first_line_of_display_with_ellipsis(); - let b_ty_string = b_field.field.ty.first_line_of_display_with_ellipsis(); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent}{:3} {}: {a_ty_string} align={}", - a_field.rel_byte_offset, - a_field.field.name, - a_field.field.align().as_u32() - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent}{:3} {}: {b_ty_string} align={}", - b_field.rel_byte_offset, - b_field.field.name, - b_field.field.align().as_u32() - ); - color_reset(f); - writeln!( - f, - "field offset is different on {a_name} ({}) and {b_name} ({}).", - a_field.rel_byte_offset, b_field.rel_byte_offset - ); - return Err(MismatchWasFound); - } - let offset = a_field.rel_byte_offset; - let a_field = &a_field.field; - let b_field = &b_field.field; - let field = &a_field; - - if offsets_match { - write!(f, "{:width$}{indent}{offset:3} ", ' ', width = pad_width); - } else { - write!(f, "{:width$}{indent} ? ", ' ', width = pad_width); - } - if a_field.name != b_field.name { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent} {}: …", a_field.name); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent} {}: …", b_field.name); - color_reset(f); - writeln!( - f, - "identifier mismatch, either\nfield '{}' is missing on {a_name}, or\nfield '{}' is missing on {b_name}.", - b_field.name, a_field.name - ); - return Err(MismatchWasFound); - } - write!(f, "{}: ", field.name); - if a_field.ty != b_field.ty { - Self::write( - &format!("{indent}{tab}"), - [(a_name, &a_field.ty), (b_name, &b_field.ty)], - colored, - f, - )?; - return Err(MismatchWasFound); - } - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - if a_field.byte_size() != b_field.byte_size() { - writeln!(f); - color_a(f); - writeln!( - f, - "{a_name}{SEP}{indent} size={}", - a_field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent} size={}", - b_field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - color_reset(f); - return Err(MismatchWasFound); - } - - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - } - (Some(a_field), None) => { - let offset = a_field.rel_byte_offset; - let a_field = &a_field.field; - let field = &a_field; - color_a(f); - write!(f, "{a_name}{SEP}{indent}{offset:3} "); - write!(f, "{}: ", field.name); - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}", a_field.name); - color_reset(f); - return Err(MismatchWasFound); - } - (None, Some(b_field)) => { - let offset = b_field.rel_byte_offset; - let b_field = &b_field.field; - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}", b_field.name); - let field = &b_field; - color_b(f); - write!(f, "{b_name}{SEP}{indent}{offset:3} "); - write!(f, "{}: ", field.name); - write!(f, "{}", field.ty.first_line_of_display_with_ellipsis()); - write!( - f, - " size={}", - field - .byte_size() - .as_ref() - .map(|x| x as &dyn Display) - .unwrap_or(&"?" as _) - ); - writeln!(f, ","); - color_reset(f); - return Err(MismatchWasFound); - } - (None, None) => break, - } - } - - write!(f, "{:width$}{indent}}}", ' ', width = pad_width); - let align_matches = a.align() == b.align(); - let size_matches = a.byte_size() == b.byte_size(); - if !align_matches && size_matches { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}align={}", a.align().as_u32()); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}align={}", b.align().as_u32()); - color_reset(f); - return Err(MismatchWasFound); - } else { - match align_matches { - true => write!(f, " align={}", a.align().as_u32()), - false => write!(f, " align=?"), - }; - } - if !size_matches { - writeln!(f); - color_a(f); - writeln!( + use compatible_with::LayoutMismatch::{TopLevel, Struct}; + match mismatch { + TopLevel { + layout_left, + layout_right, + mismatch, + } => { + match mismatch { + TopLevelMismatch::Type => writeln!( f, - "{a_name}{SEP}{indent}size={}", - a.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) - ); - color_b(f); - writeln!( - f, - "{b_name}{SEP}{indent}size={}", - b.byte_size().as_ref().map(|x| x as &dyn Display).unwrap_or(&"?" as _) - ); - color_reset(f); - return Err(MismatchWasFound); - } else { - match a.byte_size() { - Some(size) => write!(f, " size={size}"), - None => write!(f, " size=?"), - }; + "The layouts of `{}` and `{}` do not match, because their types are semantically different.", + layout_left.short_name(), + layout_right.short_name() + )?, + TopLevelMismatch::ArrayStride { + array_left, + array_right, + } => { + let array_type_layout: TypeLayout = array_left.clone().into(); + writeln!( + f, + "The layouts of `{}` and `{}` do not match.", + layout_left.short_name(), + layout_right.short_name() + )?; + // Using array_left and array_right here, because those are the layouts + // of the deepest array stride mismatch. For example it can be that + // layout_left = Array> + // array_left = Array + // because the deepest array stride mismatch is happening on the inner Array. + writeln!( + f, + "`{}` has a stride of {}, while `{}` has a stride of {}.", + array_left.short_name(), + array_left.byte_stride, + array_right.short_name(), + array_right.byte_stride + )?; + } } - // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl - return Ok(KeepWriting); } - (S::Array(a), S::Array(b)) => { - if a.len != b.len { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - - write!( - f, - "array<…, {}>", - match a.len { - Some(n) => n as &dyn Display, - None => (&"runtime-sized") as &dyn Display, - } - ); - - //a.writeln(indent, colored, f); - writeln!(f); - color_b(f); - write!(f, "{b_name}{SEP}"); - - write!( - f, - "array<…, {}>", - match b.len { - Some(n) => n as &dyn Display, - None => (&"runtime-sized") as &dyn Display, - } - ); - - //b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } else { - write!(f, "array<"); - //ta.ty.write(&(indent.to_string() + tab), colored, f); - - Self::write( - &format!("{indent}{tab}"), - [(a_name, &ta.ty), (b_name, &tb.ty)], - colored, - f, - )?; - - if let Some(na) = na { - write!(f, ", {na}"); - } - write!(f, ">"); - - if ta.byte_stride != tb.byte_stride { - writeln!(f); - color_a(f); - writeln!(f, "{a_name}{SEP}{indent}> stride={}", ta.byte_stride); - color_b(f); - writeln!(f, "{b_name}{SEP}{indent}> stride={}", tb.byte_stride); - color_reset(f); - Err(MismatchWasFound) + Struct { + struct_left, + struct_right, + mismatch, + } => { + let use_256_color_mode = false; + let enable_color = |f_: &mut W, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset_color = |f_: &mut W| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + let hex_left = "#DF5853"; // red + let hex_right = "#9A639C"; // purple + + let mut writer_left = struct_left.writer(true); + let mut writer_right = struct_right.writer(true); + // Making sure that both writer have the same layout info offset, the max of both. + writer_left.ensure_layout_info_offset(writer_right.layout_info_offset()); + writer_right.ensure_layout_info_offset(writer_left.layout_info_offset()); + + writer_left.writeln_header(f)?; + enable_color(f, hex_left)?; + writer_left.writeln_struct_declaration(f)?; + reset_color(f)?; + enable_color(f, hex_right)?; + writer_right.writeln_struct_declaration(f)?; + reset_color(f)?; + for field_index in 0..struct_left.fields.len() { + let field_left = &struct_left.fields[field_index]; + let field_right = &struct_right.fields[field_index]; + + // We are checking every field for equality, instead of using the singular found mismatch from try_find_mismatch + if field_left.ty == field_right.ty && field_left.rel_byte_offset == field_right.rel_byte_offset { + writer_left.writeln_field(f, field_index)?; } else { - // this should never happen, returning Ok(KeepWriting) will trigger the internal error in the Display impl - write!(f, "> stride={}", ta.byte_stride); - return Ok(KeepWriting); + enable_color(f, hex_left)?; + writer_left.writeln_field(f, field_index)?; + reset_color(f)?; + enable_color(f, hex_right)?; + writer_right.writeln_field(f, field_index)?; + reset_color(f)?; } } - } - (S::Vector(_), S::Vector(_)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - (S::Matrix(_), S::Matrix(_)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - (S::PackedVector(p), S::PackedVector(p1)) => { - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) - } - ( - // its written like this so that exhaustiveness checks lead us to this match statement if a type is added - S::Structure { .. } | S::Array { .. } | S::Vector { .. } | S::Matrix { .. } | S::PackedVector { .. }, - _, - ) => { - // TypeLayoutSemantics mismatch - writeln!(f); - color_a(f); - write!(f, "{a_name}{SEP}"); - a.writeln(indent, colored, f); - color_b(f); - write!(f, "{b_name}{SEP}"); - b.writeln(indent, colored, f); - color_reset(f); - Err(MismatchWasFound) + writer_left.writeln_struct_end(f)?; } } + + Ok(Mismatch::Found) } } @@ -444,3 +173,76 @@ where }), } } + +#[test] +fn test_layout_mismatch() { + use crate as shame; + use shame as sm; + use shame::CpuLayout; + use crate::aliases::*; + + #[derive(shame::GpuLayout)] + #[cpu(ACpu)] + pub struct A { + a: u32x1, + c: sm::Array>, + b: f32x1, + } + + #[derive(shame::CpuLayout)] + #[repr(C)] + pub struct ACpu { + d: u32, + c: [[f32; 3]; 2], + b: u32, + } + + let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); + let mut drawcall = encoder.new_render_pipeline(sm::Indexing::BufferU16); + let mut group0 = drawcall.bind_groups.next(); + let a: sm::Buffer = group0.next(); + + let primitive = drawcall + .vertices + .assemble(f32x3::zero(), sm::Draw::triangle_list(sm::Winding::Ccw)); + let frag = primitive.rasterize(sm::Accuracy::Relaxed); + frag.fill(f32x3::zero()); + encoder.finish().unwrap(); +} + + +#[test] +fn test_layout_mismatch_nested() { + use crate as shame; + use shame as sm; + use shame::CpuLayout; + use crate::aliases::*; + + #[derive(shame::GpuLayout)] + #[cpu(ACpu)] + pub struct A { + a: u32x1, + c: sm::Array>, + b: f32x1, + } + + #[derive(shame::CpuLayout)] + #[repr(C)] + pub struct ACpu { + d: u32, + c: [[f32; 3]; 2], + b: u32, + } + + let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); + let mut drawcall = encoder.new_render_pipeline(sm::Indexing::BufferU16); + let mut group0 = drawcall.bind_groups.next(); + let a: sm::Buffer = group0.next(); + + let primitive = drawcall + .vertices + .assemble(f32x3::zero(), sm::Draw::triangle_list(sm::Winding::Ccw)); + let frag = primitive.rasterize(sm::Accuracy::Relaxed); + frag.fill(f32x3::zero()); + encoder.finish().unwrap(); +} diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index eb7e942..256f0d2 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -91,7 +91,9 @@ impl SizedStruct { /// Returns [`FieldOffsetsSized`], which serves as an iterator over the offsets of the /// fields of this struct. `FieldOffsetsSized::struct_byte_size_and_align` can be /// used to efficiently obtain the byte_size and align. - pub fn field_offsets(&self) -> FieldOffsetsSized { FieldOffsetsSized(FieldOffsets::new(self.fields(), self.repr)) } + pub fn field_offsets(&self) -> FieldOffsetsSized<'_> { + FieldOffsetsSized(FieldOffsets::new(self.fields(), self.repr)) + } /// Returns (byte_size, align) /// @@ -107,7 +109,31 @@ impl SizedStruct { } } +impl UnsizedStruct { + /// Returns [`FieldOffsetsUnsized`]. + /// + /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. + /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset + /// and the struct's align + pub fn field_offsets(&self) -> FieldOffsetsUnsized<'_> { + FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, self.repr) + } + + /// This is expensive as it calculates the byte align by traversing all fields recursively. + pub fn align(&self) -> U32PowerOf2 { self.field_offsets().last_field_offset_and_struct_align().1 } + + // Recursively changes all struct reprs to the given `repr`. + pub fn change_all_repr(&mut self, repr: Repr) { + self.repr = repr; + for field in &mut self.sized_fields { + field.ty.change_all_repr(repr); + } + self.last_unsized.array.change_all_repr(repr); + } +} + /// An iterator over the offsets of sized fields. +#[derive(Debug)] pub struct FieldOffsets<'a> { fields: &'a [SizedField], field_index: usize, @@ -141,6 +167,7 @@ impl<'a> FieldOffsets<'a> { /// Iterator over the field offsets of a `SizedStruct`. // The difference to `FieldOffsets` is that it also offers a `struct_byte_size_and_align` method. +#[derive(Debug)] pub struct FieldOffsetsSized<'a>(FieldOffsets<'a>); impl Iterator for FieldOffsetsSized<'_> { type Item = u64; @@ -195,28 +222,6 @@ impl<'a> FieldOffsetsUnsized<'a> { pub fn into_inner(self) -> FieldOffsets<'a> { self.sized } } -impl UnsizedStruct { - /// Returns [`FieldOffsetsUnsized`]. - /// - /// - Use [`FieldOffsetsUnsized::sized_field_offsets`] for an iterator over the sized field offsets. - /// - Use [`FieldOffsetsUnsized::last_field_offset_and_struct_align`] for the last field's offset - /// and the struct's align - pub fn field_offsets(&self) -> FieldOffsetsUnsized { - FieldOffsetsUnsized::new(&self.sized_fields, &self.last_unsized, self.repr) - } - - /// This is expensive as it calculates the byte align by traversing all fields recursively. - pub fn align(&self) -> U32PowerOf2 { self.field_offsets().last_field_offset_and_struct_align().1 } - - // Recursively changes all struct reprs to the given `repr`. - pub fn change_all_repr(&mut self, repr: Repr) { - self.repr = repr; - for field in &mut self.sized_fields { - field.ty.change_all_repr(repr); - } - self.last_unsized.array.change_all_repr(repr); - } -} #[allow(missing_docs)] impl Vector { @@ -518,8 +523,8 @@ impl StructLayoutCalculator { const fn next_field_offset(&self, field_align: U32PowerOf2, field_custom_min_align: Option) -> u64 { let field_align = Self::calculate_align(field_align, field_custom_min_align, self.repr); match (self.repr, field_custom_min_align) { - (Repr::Packed, None) => self.next_offset_min, - (Repr::Packed, Some(custom_align)) => round_up(custom_align.as_u64(), self.next_offset_min), + // Packed always returns self.next_offset_min regardless of custom_min_align + (Repr::Packed, _) => self.next_offset_min, (Repr::Storage | Repr::Uniform, _) => round_up(field_align.as_u64(), self.next_offset_min), } } @@ -926,9 +931,9 @@ mod tests { assert_eq!(calc.byte_size(), 12); assert_eq!(calc.align(), U32PowerOf2::_1); - // Add a vec2 field - but with custom min align, which overwrites packed alignment + // Add a vec2 field - but with custom min align, which is ignored because of Repr::Packed let offset3 = calc.extend(8, U32PowerOf2::_8, None, Some(U32PowerOf2::_16), false); - assert_eq!(offset3, 16); + assert_eq!(offset3, 12); // TODO(chronicl) not sure whether the alignment should stay 1 for a packesd struct // with custom min align field. assert_eq!(calc.align(), U32PowerOf2::_1); diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs index c2e6321..3f73021 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs @@ -359,6 +359,18 @@ impl TryFrom for LayoutableType { } } +#[derive(Debug, Clone)] +pub enum StructKind { + Sized(SizedStruct), + Unsized(UnsizedStruct), +} + +impl From for StructKind { + fn from(value: SizedStruct) -> Self { StructKind::Sized(value) } +} +impl From for StructKind { + fn from(value: UnsizedStruct) -> Self { StructKind::Unsized(value) } +} #[test] fn test_ir_conversion_error() { diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs index f9471c7..fc9c841 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs @@ -11,7 +11,7 @@ use crate::{ }; pub use crate::ir::{Len, Len2, PackedVector, ScalarTypeFp, ScalarTypeInteger, ir_type::CanonName}; -use super::{construction::StructKind, Repr}; +use super::{Repr}; pub(crate) mod align_size; pub(crate) mod builder; diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs b/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs index 4fc523f..ffc9d98 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs @@ -1,11 +1,10 @@ use std::rc::Rc; - use crate::{ - any::layout::{ElementLayout, FieldLayout, FieldLayoutWithOffset, Repr, StructLayout, TypeLayoutSemantics}, - frontend::rust_types::type_layout::{ArrayLayout, DEFAULT_REPR}, + frontend::rust_types::type_layout::{ + ArrayLayout, FieldLayout, MatrixLayout, PackedVectorLayout, Repr, StructLayout, VectorLayout, DEFAULT_REPR, + }, ir, TypeLayout, }; - use super::{ Atomic, LayoutableType, Matrix, PackedVector, RuntimeSizedArray, SizedArray, SizedField, SizedStruct, SizedType, UnsizedStruct, Vector, @@ -17,8 +16,8 @@ impl LayoutableType { pub fn layout_with_default_repr(&self, default_repr: Repr) -> TypeLayout { match self { LayoutableType::Sized(ty) => ty.layout(default_repr), - LayoutableType::UnsizedStruct(ty) => ty.layout(default_repr), - LayoutableType::RuntimeSizedArray(ty) => ty.layout(default_repr), + LayoutableType::UnsizedStruct(ty) => ty.layout().into(), + LayoutableType::RuntimeSizedArray(ty) => ty.layout(default_repr).into(), } } } @@ -26,74 +25,71 @@ impl LayoutableType { impl SizedType { pub fn layout(&self, parent_repr: Repr) -> TypeLayout { match &self { - SizedType::Vector(v) => v.layout(parent_repr), - SizedType::Atomic(a) => a.layout(parent_repr), - SizedType::Matrix(m) => m.layout(parent_repr), - SizedType::Array(a) => a.layout(parent_repr), - SizedType::PackedVec(v) => v.layout(parent_repr), - SizedType::Struct(s) => s.layout(parent_repr), + SizedType::Vector(v) => v.layout(parent_repr).into(), + SizedType::Atomic(a) => a.layout(parent_repr).into(), + SizedType::Matrix(m) => m.layout(parent_repr).into(), + SizedType::Array(a) => a.layout(parent_repr).into(), + SizedType::PackedVec(v) => v.layout(parent_repr).into(), + SizedType::Struct(s) => s.layout(parent_repr).into(), } } } impl Vector { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { - TypeLayout::new( - self.byte_size(parent_repr).into(), - self.align(parent_repr), - TypeLayoutSemantics::Vector(*self), - ) + pub fn layout(&self, parent_repr: Repr) -> VectorLayout { + VectorLayout { + byte_size: self.byte_size(parent_repr), + align: self.align(parent_repr).into(), + ty: *self, + debug_is_atomic: false, + } } } impl Matrix { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { - TypeLayout::new( - self.byte_size(parent_repr).into(), - self.align(parent_repr), - TypeLayoutSemantics::Matrix(*self), - ) + pub fn layout(&self, parent_repr: Repr) -> MatrixLayout { + MatrixLayout { + byte_size: self.byte_size(parent_repr), + align: self.align(parent_repr).into(), + ty: *self, + } } } impl Atomic { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + pub fn layout(&self, parent_repr: Repr) -> VectorLayout { // Atomic types are represented as vectors of length 1. let vector = Vector::new(self.scalar.into(), ir::Len::X1); - TypeLayout::new( - vector.byte_size(parent_repr).into(), - vector.align(parent_repr), - TypeLayoutSemantics::Vector(vector), - ) + let mut layout = vector.layout(parent_repr); + layout.debug_is_atomic = true; + layout } } -impl SizedArray { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { - TypeLayout::new( - self.byte_size(parent_repr).into(), - self.align(parent_repr), - TypeLayoutSemantics::Array(Rc::new(ArrayLayout { - byte_stride: self.byte_stride(parent_repr), - element_ty: self.element.layout(parent_repr), - len: Some(self.len.get()), - })), - ) +impl PackedVector { + pub fn layout(&self, parent_repr: Repr) -> PackedVectorLayout { + PackedVectorLayout { + byte_size: self.byte_size().as_u64(), + align: self.align(parent_repr).into(), + ty: *self, + } } } -impl PackedVector { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { - TypeLayout::new( - self.byte_size().as_u64().into(), - self.align(parent_repr), - TypeLayoutSemantics::PackedVector(*self), - ) +impl SizedArray { + pub fn layout(&self, parent_repr: Repr) -> ArrayLayout { + ArrayLayout { + byte_size: self.byte_size(parent_repr).into(), + align: self.align(parent_repr).into(), + byte_stride: self.byte_stride(parent_repr), + element_ty: self.element.layout(parent_repr), + len: Some(self.len.get()), + } } } impl SizedStruct { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + pub fn layout(&self, parent_repr: Repr) -> StructLayout { let mut field_offsets = self.field_offsets(); let fields = (&mut field_offsets) .zip(self.fields()) @@ -102,76 +98,67 @@ impl SizedStruct { let (byte_size, align) = field_offsets.struct_byte_size_and_align(); - TypeLayout::new( - Some(byte_size), - align, - TypeLayoutSemantics::Structure(Rc::new(StructLayout { - name: self.name.clone().into(), - fields, - })), - ) + StructLayout { + byte_size: Some(byte_size), + align: align.into(), + name: self.name.clone().into(), + fields, + } } } -fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayoutWithOffset { +fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayout { let mut ty = field.ty.layout(repr); // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account // custom_min_align and custom_min_size, but field.byte_size and field.align do. - ty.byte_size = Some(field.byte_size(repr)); - ty.align = field.align(repr).into(); - FieldLayoutWithOffset { + ty.set_byte_size(Some(field.byte_size(repr))); + ty.set_align(field.align(repr)); + + FieldLayout { rel_byte_offset: offset, - field: FieldLayout { - name: field.name.clone(), - ty, - }, + name: field.name.clone(), + ty, } } impl UnsizedStruct { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { + pub fn layout(&self) -> StructLayout { let mut field_offsets = self.field_offsets(); let mut fields = (&mut field_offsets.sized_field_offsets()) .zip(self.sized_fields.iter()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, parent_repr)) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, self.repr)) .collect::>(); let (field_offset, align) = field_offsets.last_field_offset_and_struct_align(); - let mut ty = self.last_unsized.array.layout(parent_repr); + let mut ty = self.last_unsized.array.layout(self.repr); // VERY IMPORTANT: TypeLayout::from_runtime_sized_array does not take into account // custom_min_align, but s.last_unsized.align does. - ty.align = self.last_unsized.align(parent_repr).into(); + ty.align = self.last_unsized.align(self.repr).into(); - fields.push(FieldLayoutWithOffset { + fields.push(FieldLayout { rel_byte_offset: field_offset, - field: FieldLayout { - name: self.last_unsized.name.clone(), - ty, - }, + name: self.last_unsized.name.clone(), + ty: ty.into(), }); - TypeLayout::new( - None, - align, - TypeLayoutSemantics::Structure(Rc::new(StructLayout { - name: self.name.clone().into(), - fields, - })), - ) + StructLayout { + byte_size: None, + align: align.into(), + name: self.name.clone().into(), + fields, + } } } impl RuntimeSizedArray { - pub fn layout(&self, parent_repr: Repr) -> TypeLayout { - TypeLayout::new( - None, - self.align(parent_repr), - TypeLayoutSemantics::Array(Rc::new(ArrayLayout { - byte_stride: self.byte_stride(parent_repr), - element_ty: self.element.layout(parent_repr), - len: None, - })), - ) + pub fn layout(&self, parent_repr: Repr) -> ArrayLayout { + ArrayLayout { + byte_size: None, + align: self.align(parent_repr).into(), + byte_stride: self.byte_stride(parent_repr), + element_ty: self.element.layout(parent_repr), + len: None, + } } } diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index f2f5ff9..8e30e8b 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] //! Everything related to type layouts. use std::{ @@ -8,7 +9,7 @@ use std::{ }; use crate::{ - any::{layout::repr::Packed, U32PowerOf2}, + any::{U32PowerOf2}, call_info, common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, ir::{ @@ -24,27 +25,41 @@ use layoutable::{ }; pub(crate) mod compatible_with; -pub(crate) mod construction; pub(crate) mod eq; pub(crate) mod layoutable; pub const DEFAULT_REPR: Repr = Repr::Storage; -/// The type contained in the bytes of a `TypeLayout`. +/// The memory layout of a type. +/// +/// This models only the layout, not other characteristics of the types. +/// For example an `Atomic>` is treated like a regular `vec` layout wise. +/// +/// ### Layout comparison +/// +/// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question +/// "do these two types have the same layout" so that uploading a type to the gpu +/// will result in no memory errors. +/// +/// a layout comparison looks like this: +/// ``` +/// use shame as sm; +/// assert_eq!(sm::cpu_layout::(), sm::gpu_layout>()); +/// ``` #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TypeLayoutSemantics { +pub enum TypeLayout { /// `vec` - Vector(Vector), + Vector(VectorLayout), /// special compressed vectors for vertex attribute types /// /// see the [`crate::packed`] module - PackedVector(PackedVector), + PackedVector(PackedVectorLayout), /// `mat`, first `Len2` is cols, 2nd `Len2` is rows - Matrix(Matrix), + Matrix(MatrixLayout), /// `Array` and `Array>` Array(Rc), /// structures which may be empty and may have an unsized last field - Structure(Rc), + Struct(Rc), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -52,6 +67,9 @@ pub struct VectorLayout { pub byte_size: u64, pub align: IgnoreInEqOrdHash, pub ty: Vector, + + // debug information + pub debug_is_atomic: bool, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -70,236 +88,224 @@ pub struct MatrixLayout { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayLayout { + pub byte_size: Option, + pub align: IgnoreInEqOrdHash, pub byte_stride: u64, - // Rc here and not in TypeLayoutSemnatics, so that matches like - // TypeLayoutSemantics::Array(Array { len: Some(n), .. }) = layout.kind are possible. pub element_ty: TypeLayout, // not NonZeroU32, since for rust `CpuLayout`s the array size may be 0. pub len: Option, } -/// The memory layout of a type. -/// -/// This models only the layout, not other characteristics of the types. -/// For example an `Atomic>` is treated like a regular `vec` layout wise. -/// -/// ### Layout comparison -/// -/// The `PartialEq + Eq` implementation of `TypeLayout` is designed to answer the question -/// "do these two types have the same layout" so that uploading a type to the gpu -/// will result in no memory errors. -/// -/// a layout comparison looks like this: -/// ``` -/// use shame as sm; -/// assert_eq!(sm::cpu_layout::(), sm::gpu_layout>()); -/// ``` -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct TypeLayout { - /// size in bytes (Some), or unsized (None) +/// a sized or unsized struct type with 0 or more fields +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StructLayout { pub byte_size: Option, - /// the byte alignment - /// - /// top level alignment is not considered relevant in some checks, but relevant in others (vertex array elements) pub align: IgnoreInEqOrdHash, - /// the type contained in the bytes of this type layout - pub kind: TypeLayoutSemantics, + /// The canonical name of the structure type, ignored in equality/hash comparisons + pub name: IgnoreInEqOrdHash, + /// The fields of the structure with their memory offsets + pub fields: Vec, } -/// A version of TypeLayout that provides additional compile-time guarantees. -/// It is guaranteed to represent a LayoutableType that is layed out in memory using T's layout rules. -/// -/// An instance of `TypeLayout` (which drops compile time guarantees) can be obtained via `GpuTypeLayout::layout`. -/// -/// The following types implement `TypeRepr` and can be found in [`shame::any::repr`]: -/// -/// ``` ignore -/// struct Storage; /// wgsl storage layout rules -/// struct Uniform; /// wgsl uniform layout rules -/// struct Packed; /// Packed layout -/// -/// use shame::any::layout::GpuTypeLayout; -/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type -/// /// which can be used in the storage address space. -/// GpuTypeLayout -/// /// Guarantees that the corresponding `TypeLayout` can be represent by a wgsl type -/// /// which can be used in the uniform address space. -/// GpuTypeLayout -/// /// Can only be used in vertex buffers and is packed. -/// GpuTypeLayout -/// ``` -/// -/// # Construction -/// -/// While `GpuTypeLayout` and `GpuTypeLayout` can be freely created -/// from a `LayoutableType`, the only way to get a `GpuTypeLayout` is by -/// using `TryFrom::try_from` on a `GpuTypeLayout`, which only succeeds if -/// the storage layout also follows the uniform layout rules - it does not change the -/// corresponding `TypeLayout`. -/// -/// # Example -/// ``` -/// use shame as sm; -/// use shame::prelude::*; -/// use shame::aliases::*; -/// use shame::any::layout::{GpuTypeLayout, SizedStruct, Layoutable, repr}; -/// -/// // We replicate this struct's `GpuLayout` using `shame::any::layout` types. -/// #[derive(sm::GpuLayout)] -/// struct Vertex { -/// position: f32x3, -/// normal: f32x3, -/// uv: f32x2, -/// } -/// -/// // SizedStruct::new immediately takes the first field of the struct, because -/// // structs need to have at least one field. -/// let sized_struct = SizedStruct::new("Vertex", "position", f32x3::layoutable_type_sized()) -/// .extend("normal", f32x3::layoutable_type_sized()) -/// .extend("uv", f32x1::layoutable_type_sized()); -/// -/// let storage = GpuTypeLayout::::new(sized_struct.clone()); -/// let packed = GpuTypeLayout::::new(sized_struct); -/// assert_ne!(storage.layout(), packed.layout()); -/// -/// // Does not exist: -/// // let uniform = GpuTypeLayout::::new(sized_struct.clone()); -/// -/// // However we can try to upgrade a GpuTypeLayout:: -/// let uniform = GpuTypeLayout::::try_from(storage.clone()).unwrap(); -/// -/// // Which if it succeeds, guarantees: -/// assert_eq!(storage.layout(), uniform.layout()); -/// ``` -#[derive(Debug, Clone)] -pub struct GpuTypeLayout { - ty: LayoutableType, - _repr: PhantomData, +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldLayout { + /// The relative byte offset of this field from the start of its containing structure + pub rel_byte_offset: u64, + pub name: CanonName, + pub ty: TypeLayout, } -impl GpuTypeLayout { - /// Get the TypeLayout and remove compile time guarantees about the TypeRepr". - pub fn layout(&self) -> TypeLayout { todo!() } - /// Returns the `LayoutableType` this `GpuTypeLayout` is based on. - pub fn layoutable_type(&self) -> &LayoutableType { &self.ty } +/// Enum of layout rules. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Repr { + /// Wgsl storage address space layout + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Storage, + /// Wgsl uniform address space layout + /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints + Uniform, + /// Packed layout. Vertex buffer only. + Packed, } -use repr::DerivableRepr; -pub use repr::{TypeRepr, Repr}; -/// Module for all restrictions on `GpuTypeLayout`. -pub mod repr { - use super::*; +impl std::fmt::Display for Repr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Repr::Storage => write!(f, "storage"), + Repr::Uniform => write!(f, "uniform"), + Repr::Packed => write!(f, "packed"), + } + } +} - /// Implemented by marker types (such as [sm::repr::Storage] [sm::repr::Packed]), - /// which represent rulesets to lay out types in memory. - /// The user specifies these on the highest level via the #[gpu_repr(...)] attribute. - /// - /// See [`GpuTypeLayout`] documentation for more details. - pub trait TypeRepr: Clone + PartialEq + Eq { - /// The corresponding enum variant of `Repr`. - const REPR: Repr; +impl ArrayLayout { + pub fn short_name(&self) -> String { + match self.len { + Some(n) => format!("array<{}, {n}>", self.element_ty.short_name()), + None => format!("array<{}, runtime-sized>", self.element_ty.short_name()), + } } - /// A subset of the types implementing `TypeRepr`, which are derivable. - pub trait DerivableRepr: TypeRepr {} - impl DerivableRepr for Storage {} - impl DerivableRepr for Packed {} - - /// Enum of layout rules. - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum Repr { - /// Wgsl storage address space layout - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Storage, - /// Wgsl uniform address space layout - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Uniform, - /// Packed layout. Vertex buffer only. - Packed, +} + +pub(crate) fn align_to_string(align: U32PowerOf2) -> String { align.as_u32().to_string() } +pub(crate) fn byte_size_to_string(size: Option) -> String { size.map(|s| s.to_string()).unwrap_or("None".into()) } +fn write_indent(mut f: &mut W, n: usize) -> std::fmt::Result { + for _ in 0..n { + f.write_char(' ')? } + Ok(()) +} +pub struct StructWriter<'a> { + s: &'a StructLayout, + tab: &'static str, + include_layout_info: bool, + layout_info_offset: usize, +} - impl Repr { - /// True if `Repr::Storage` - pub const fn is_storage(self) -> bool { matches!(self, Repr::Storage) } - /// True if `Repr::Uniform` - pub const fn is_uniform(self) -> bool { matches!(self, Repr::Uniform) } - /// True if `Repr::Packed` - pub const fn is_packed(self) -> bool { matches!(self, Repr::Packed) } +impl<'a> StructWriter<'a> { + pub fn new(s: &'a StructLayout, include_layout_info: bool) -> Self { + let mut this = Self { + s, + tab: " ", + include_layout_info, + layout_info_offset: 0, + }; + let layout_info_offset = this.struct_declaration().len().max( + (0..this.s.fields.len()) + .map(|i| this.field_declaration(i).len()) + .max() + .unwrap_or(0), + ); + this.ensure_layout_info_offset(layout_info_offset); + this } - impl std::fmt::Display for Repr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Repr::Storage => write!(f, "storage"), - Repr::Uniform => write!(f, "uniform"), - Repr::Packed => write!(f, "packed"), - } + pub(crate) fn layout_info_offset(&self) -> usize { self.layout_info_offset } + + pub(crate) fn ensure_layout_info_offset(&mut self, min_layout_info_offset: usize) { + self.layout_info_offset = self.layout_info_offset.max(min_layout_info_offset) + } + + fn struct_declaration(&self) -> String { format!("struct {} {{", self.s.name) } + + fn field_declaration(&self, field_index: usize) -> String { + match self.s.fields.get(field_index) { + Some(field) => format!("{}{}: {},", self.tab, field.name, field.ty.short_name()), + None => String::new(), } } - macro_rules! type_repr { - ($($repr:ident),*) => { - $( - /// A type representation used by `GpuTypeLayout`. - /// See [`GpuTypeLayout`] documentation for more details. - #[derive(Clone, PartialEq, Eq, Hash)] - pub struct $repr; - impl TypeRepr for $repr { - const REPR: Repr = Repr::$repr; - } - )* + pub(crate) fn writeln_header(&self, f: &mut W) -> std::fmt::Result { + self.write_header(f)?; + writeln!(f) + } + + pub(crate) fn writeln_struct_declaration(&self, f: &mut W) -> std::fmt::Result { + self.write_struct_declaration(f)?; + writeln!(f) + } + + pub(crate) fn writeln_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { + self.write_field(f, field_index)?; + writeln!(f) + } + + pub(crate) fn writeln_struct_end(&self, f: &mut W) -> std::fmt::Result { + self.write_struct_end(f)?; + writeln!(f) + } + + pub(crate) fn write_header(&self, f: &mut W) -> std::fmt::Result { + use TypeLayout::*; + let has_array_field = self.s.fields.iter().any(|f| match f.ty { + Array(_) => true, + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => false, + }); + let layout_info = if has_array_field { + "offset align size stride" + } else { + "offset align size" }; + if self.include_layout_info { + write!( + f, + "{:info_offset$} {layout_info}", + "", + info_offset = self.layout_info_offset() + ) + } else { + Ok(()) + } } - type_repr!(Storage, Uniform, Packed); -} -impl TypeLayout { - pub(crate) fn new(byte_size: Option, byte_align: U32PowerOf2, kind: TypeLayoutSemantics) -> Self { - TypeLayout { - byte_size, - align: byte_align.into(), - kind, + pub(crate) fn write_struct_declaration(&self, f: &mut W) -> std::fmt::Result { + if self.include_layout_info { + write!( + f, + "{:info_offset$} {:>6} {:>5} {:>4}", + self.struct_declaration(), + "", + align_to_string(*self.s.align), + byte_size_to_string(self.s.byte_size), + info_offset = self.layout_info_offset() + ) + } else { + write!(f, "{}", self.struct_declaration()) } } -} -/// a sized or unsized struct type with 0 or more fields -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StructLayout { - /// The canonical name of the structure type, ignored in equality/hash comparisons - pub name: IgnoreInEqOrdHash, - /// The fields of the structure with their memory offsets - pub fields: Vec, + pub(crate) fn write_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { + let field = &self.s.fields[field_index]; + if self.include_layout_info { + write!( + f, + "{:info_offset$} {:>6} {:>5} {:>4}", + self.field_declaration(field_index), + field.rel_byte_offset, + align_to_string(field.ty.align()), + byte_size_to_string(field.ty.byte_size()), + info_offset = self.layout_info_offset() + )?; + match &field.ty { + TypeLayout::Array(a) => write!(f, " {:>5}", a.byte_stride), + TypeLayout::Vector(_) | TypeLayout::PackedVector(_) | TypeLayout::Matrix(_) | TypeLayout::Struct(_) => { + Ok(()) + } + } + } else { + write!(f, "{}", self.field_declaration(field_index)) + } + } + + pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } } impl StructLayout { - /// this exists, because if in the future a refactor happens that separates - /// fields into sized and unsized fields, the intention of this function is - /// clear - fn all_fields(&self) -> &[FieldLayoutWithOffset] { &self.fields } -} + pub fn short_name(&self) -> String { self.name.to_string() } -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FieldLayoutWithOffset { - /// The layout information for the field - pub field: FieldLayout, - /// The relative byte offset of this field from the start of its containing structure - pub rel_byte_offset: u64, -} + pub(crate) fn to_string_with_layout_information(&self) -> Result { + let mut s = String::new(); + self.write(&mut s, true)?; + Ok(s) + } -impl std::ops::Deref for FieldLayoutWithOffset { - type Target = FieldLayout; - fn deref(&self) -> &Self::Target { &self.field } -} + pub(crate) fn writer(&self, include_layout_info: bool) -> StructWriter<'_> { + StructWriter::new(self, include_layout_info) + } -/// Describes the layout of the elements of an array. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ElementLayout { - /// Stride of the elements - pub byte_stride: u64, - /// The type layout of each element in the array. - pub ty: TypeLayout, + pub(crate) fn write(&self, f: &mut W, include_layout_info: bool) -> std::fmt::Result { + use TypeLayout::*; + + let mut writer = self.writer(include_layout_info); + writer.writeln_header(f)?; + writer.writeln_struct_declaration(f)?; + for i in 0..self.fields.len() { + writer.writeln_field(f, i)?; + } + writer.writeln_struct_end(f) + } } impl TypeLayout { @@ -307,229 +313,146 @@ impl TypeLayout { /// /// For sized types, this returns Some(size), while for unsized types /// (like runtime-sized arrays), this returns None. - pub fn byte_size(&self) -> Option { self.byte_size } - - /// Returns the alignment requirement of the represented type. - pub fn align(&self) -> U32PowerOf2 { *self.align } - - /// a short name for this `TypeLayout`, useful for printing inline - pub fn short_name(&self) -> String { - match &self.kind { - TypeLayoutSemantics::Vector { .. } | - TypeLayoutSemantics::PackedVector { .. } | - TypeLayoutSemantics::Matrix { .. } => format!("{}", self), - TypeLayoutSemantics::Array(a) => match a.len { - Some(n) => format!("array<{}, {n}>", a.element_ty.short_name()), - None => format!("array<{}, runtime-sized>", a.element_ty.short_name()), - }, - TypeLayoutSemantics::Structure(s) => s.name.to_string(), + pub fn byte_size(&self) -> Option { + match self { + TypeLayout::Vector(v) => Some(v.byte_size), + TypeLayout::PackedVector(p) => Some(p.byte_size), + TypeLayout::Matrix(m) => Some(m.byte_size), + TypeLayout::Array(a) => a.byte_size, + TypeLayout::Struct(s) => s.byte_size, } } - pub(crate) fn first_line_of_display_with_ellipsis(&self) -> String { - let string = format!("{}", self); - string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) - } - - pub(crate) fn writeln(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { - self.write(indent, colored, f)?; - writeln!(f) + /// Returns the alignment requirement of the represented type. + pub fn align(&self) -> U32PowerOf2 { + match self { + TypeLayout::Vector(v) => *v.align, + TypeLayout::PackedVector(p) => *p.align, + TypeLayout::Matrix(m) => *m.align, + TypeLayout::Array(a) => *a.align, + TypeLayout::Struct(s) => *s.align, + } } - //TODO(low prio) try to figure out a cleaner way of writing these. - pub(crate) fn write(&self, indent: &str, colored: bool, f: &mut W) -> std::fmt::Result { - let tab = " "; - let use_256_color_mode = false; - let color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), - }; - let reset = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), - }; - - use TypeLayoutSemantics as Sem; - - match &self.kind { - Sem::Vector(Vector { len: l, scalar: t }) => match l { - Len::X1 => write!(f, "{t}")?, - l => write!(f, "{t}x{}", u64::from(*l))?, - }, - Sem::PackedVector(c) => write!(f, "{}", c)?, - Sem::Matrix(m) => write!(f, "{}", ir::SizedType::Matrix(m.columns, m.rows, m.scalar))?, - Sem::Array(a) => { - let stride = a.byte_stride; - write!(f, "array<")?; - a.element_ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(n) = a.len { - write!(f, ", {n}")?; + /// If self is sized and `byte_size` is None, the size is not overwritten. + pub fn set_byte_size(&mut self, byte_size: Option) { + match self { + TypeLayout::Vector(v) => { + if let Some(size) = byte_size { + v.byte_size = size; } - write!(f, "> stride={stride}")?; } - Sem::Structure(s) => { - writeln!(f, "struct {} {{", s.name)?; - { - let indent = indent.to_string() + tab; - for field in &s.fields { - let offset = field.rel_byte_offset; - let field = &field.field; - write!(f, "{indent}{offset:3} {}: ", field.name)?; - field.ty.write(&(indent.to_string() + tab), colored, f)?; - if let Some(size) = field.ty.byte_size { - write!(f, " size={size}")?; - } else { - write!(f, " size=?")?; - } - writeln!(f, ",")?; - } + TypeLayout::Matrix(m) => { + if let Some(size) = byte_size { + m.byte_size = size; } - write!(f, "{indent}}}")?; - write!(f, " align={}", self.align.as_u64())?; - if let Some(size) = self.byte_size { - write!(f, " size={size}")?; - } else { - write!(f, " size=?")?; + } + TypeLayout::PackedVector(v) => { + if let Some(size) = byte_size { + v.byte_size = size; } } - }; - Ok(()) + TypeLayout::Array(a) => { + let mut array = (**a).clone(); + array.byte_size = byte_size; + *a = Rc::new(array); + } + TypeLayout::Struct(s) => { + let mut struct_ = (**s).clone(); + struct_.byte_size = byte_size; + *s = Rc::new(struct_); + } + } } - fn to_string_plain(&self) -> String { - use TypeLayoutSemantics::*; - - match &self.kind { - Vector(v) => v.to_string(), - PackedVector(c) => c.to_string(), - Matrix(m) => m.to_string(), - Array(a) => { - if let Some(n) = a.len { - format!("array<{}, {n}>", a.element_ty.to_string_plain()) - } else { - format!("array<{}>", a.element_ty.to_string_plain()) - } + pub fn set_align(&mut self, align: U32PowerOf2) { + let align = align.into(); + match self { + TypeLayout::Vector(v) => { + v.align = align; + } + TypeLayout::Matrix(m) => { + m.align = align; + } + TypeLayout::PackedVector(v) => { + v.align = align; + } + TypeLayout::Array(a) => { + let mut array = (**a).clone(); + array.align = align; + *a = Rc::new(array); + } + TypeLayout::Struct(s) => { + let mut struct_ = (**s).clone(); + struct_.align = align; + *s = Rc::new(struct_); } - Structure(s) => format!("{}", s.name), + } + } + + /// a short name for this `TypeLayout`, useful for printing inline + pub fn short_name(&self) -> String { + use TypeLayout::*; + + match &self { + Vector(v) => v.ty.to_string(), + PackedVector(v) => v.ty.to_string(), + Matrix(m) => m.ty.to_string(), + Array(a) => a.short_name(), + Struct(s) => s.short_name(), } } pub(crate) fn to_string_with_layout_information(&self) -> Result { let mut s = String::new(); - self.write2(&mut s, true)?; + self.write(&mut s, true)?; Ok(s) } - pub(crate) fn write2(&self, f: &mut W, include_layout_info: bool) -> std::fmt::Result { - use TypeLayoutSemantics::*; - let tab = " "; - - let struct_decl = |s: &StructLayout| format!("struct {} {{", s.name); - let mut field_decl = - |field: &FieldLayoutWithOffset| format!("{tab}{}: {},", field.name, field.ty.to_string_plain()); - - if !include_layout_info { - match &self.kind { - Vector(_) | PackedVector(_) | Matrix(_) | Array(_) => writeln!(f, "{}", self.to_string_plain())?, - Structure(s) => { - writeln!(f, "{}", struct_decl(s))?; - for field in s.fields.iter() { - writeln!(f, "{}", field_decl(field))?; - } - } - } - } else { - let align = |layout: &TypeLayout| layout.align.as_u32(); - let size = |layout: &TypeLayout| layout.byte_size.map(|s| s.to_string()).unwrap_or("None".into()); - - let indent = |f: &mut W, n: usize| -> std::fmt::Result { - for _ in 0..n { - f.write_char(' ')? - } - Ok(()) - }; - - match &self.kind { - Vector(_) | PackedVector(_) | Matrix(_) => { - let plain = self.to_string_plain(); + pub(crate) fn write(&self, f: &mut W, include_layout_info: bool) -> std::fmt::Result { + use TypeLayout::*; + let align = |layout: &TypeLayout| align_to_string(layout.align()); + let size = |layout: &TypeLayout| byte_size_to_string(layout.byte_size()); - // Write header - indent(f, plain.len() + 1)?; - f.write_str("align size\n")?; + let (stride, header) = match self { + Array(a) => (Some(a.byte_stride), "align size stride"), + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => (None, "align size"), + }; - // Write type and layout info - writeln!(f, "{plain} {:5} {:4}", align(self), size(self))?; - } - Array(a) => { - let plain = self.to_string_plain(); + match self { + Vector(_) | PackedVector(_) | Matrix(_) | Array(_) => { + let plain = self.short_name(); + if include_layout_info { // Write header - indent(f, plain.len() + 1)?; - f.write_str("align size stride\n")?; + write_indent(f, plain.len() + 1)?; + f.write_str(header)?; + writeln!(f); // Write type and layout info - writeln!(f, "{plain} {:5} {:4} {:6}", align(self), size(self), a.byte_stride)?; - } - Structure(s) => { - let has_array_field = s.fields.iter().any(|f| match f.ty.kind { - Array(_) => true, - Vector(_) | PackedVector(_) | Matrix(_) | Structure(_) => false, - }); - let layout_info = if has_array_field { - "offset align size stride" + write!(f, "{plain} {:5} {:4}", align(self), size(self))?; + if let Some(stride) = stride { + writeln!(f, " {stride}")?; } else { - "offset align size" - }; - - let max_line_len = 1 + s - .fields - .iter() - .map(field_decl) - .map(|s| s.len()) - .max() - .unwrap_or(0) - .max(struct_decl(s).len()); - - // Write header - indent(f, max_line_len); - writeln!(f, "{layout_info} {:6} {:5} {:4}", "", align(self), size(self))?; - - // Write struct and layout info - writeln!(f, "{}", struct_decl(s))?; - for field in s.fields.iter() { - write!( - f, - "{} {:6} {:5} {:4}", - field_decl(field), - field.rel_byte_offset, - align(&field.ty), - size(&field.ty), - )?; - match &field.ty.kind { - Array(a) => writeln!(f, " {:6}", a.byte_stride)?, - Vector(_) | PackedVector(_) | Matrix(_) | Structure(_) => writeln!(f, "")?, - } + writeln!(f)? } + } else { + writeln!(f, "{plain}")? } - }; - } - + } + Struct(s) => s.write(f, include_layout_info)?, + }; Ok(()) } -} -impl TypeLayout { - pub(crate) fn from_rust_sized(kind: TypeLayoutSemantics) -> Self { - Self::new( - Some(size_of::() as u64), - // align is always a power of 2: - // https://doc.rust-lang.org/reference/type-layout.html#r-layout.properties.align - U32PowerOf2::try_from(align_of::() as u32).unwrap(), - kind, - ) + pub(crate) fn first_line_of_display_with_ellipsis(&self) -> String { + let string = format!("{}", self); + string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) } +} +impl TypeLayout { // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings pub(crate) fn from_store_ty( store_type: ir::StoreType, @@ -539,27 +462,245 @@ impl TypeLayout { } } -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FieldLayout { - pub name: CanonName, - pub ty: TypeLayout, +impl From for TypeLayout { + fn from(layout: VectorLayout) -> Self { TypeLayout::Vector(layout) } +} + +impl From for TypeLayout { + fn from(layout: PackedVectorLayout) -> Self { TypeLayout::PackedVector(layout) } +} + +impl From for TypeLayout { + fn from(layout: MatrixLayout) -> Self { TypeLayout::Matrix(layout) } } -impl FieldLayout { - fn byte_size(&self) -> Option { self.ty.byte_size() } +impl From for TypeLayout { + fn from(layout: ArrayLayout) -> Self { TypeLayout::Array(Rc::new(layout)) } +} - /// The alignment of the field with `custom_min_align` taken into account. - fn align(&self) -> U32PowerOf2 { self.ty.align() } +impl From for TypeLayout { + fn from(layout: StructLayout) -> Self { TypeLayout::Struct(Rc::new(layout)) } } + impl Display for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let colored = Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false); - self.write("", colored, f) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, true) } } -impl Debug for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write("", false, f) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + any::U32PowerOf2, + frontend::rust_types::type_layout::{ + layoutable::{*}, + Repr, *, + }, + }; + use std::{rc::Rc, num::NonZeroU32}; + + #[test] + fn test_array_alignment() { + let array: LayoutableType = SizedArray::new( + Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), + NonZeroU32::new(1).unwrap(), + ) + .into(); + + // To change the top level arrays repr, we need to set the default repr, + // because non-structs inherit repr. + let storage = array.layout_with_default_repr(Repr::Storage); + let uniform = array.layout_with_default_repr(Repr::Uniform); + let packed = array.layout_with_default_repr(Repr::Packed); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(4)); + assert_eq!(uniform.byte_size(), Some(16)); + assert_eq!(packed.byte_size(), Some(4)); + + match (storage, uniform, packed) { + (TypeLayout::Array(storage), TypeLayout::Array(uniform), TypeLayout::Array(packed)) => { + assert_eq!(storage.len, Some(1)); + assert_eq!(uniform.len, Some(1)); + assert_eq!(packed.len, Some(1)); + assert_eq!(storage.byte_stride, 4); + assert_eq!(uniform.byte_stride, 16); + assert_eq!(packed.byte_stride, 4); + } + _ => panic!("Unexpected layout kind"), + } + } + + #[test] + fn test_struct_alignment() { + let s = + |repr| -> LayoutableType { SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1), repr).into() }; + + let storage = s(Repr::Storage).layout(); + let uniform = s(Repr::Uniform).layout(); + let packed = s(Repr::Packed).layout(); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(4)); + assert_eq!(uniform.byte_size(), Some(16)); + assert_eq!(packed.byte_size(), Some(4)); + } + + #[test] + fn test_nested_struct_field_offset() { + let s = |repr| -> LayoutableType { + let a = SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1), repr); + SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1), repr) + .extend("b", a) // offset 4 for storage and packed, offset 16 for uniform + .into() + }; + + let storage = s(Repr::Storage).layout(); + let uniform = s(Repr::Uniform).layout(); + let packed = s(Repr::Packed).layout(); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(8)); + // field b is bytes 16..=19 and struct size must be a multiple of the struct align (16) + assert_eq!(uniform.byte_size(), Some(32)); + assert_eq!(packed.byte_size(), Some(8)); + + match (storage, uniform, packed) { + (TypeLayout::Struct(storage), TypeLayout::Struct(uniform), TypeLayout::Struct(packed)) => { + assert_eq!(storage.fields[1].rel_byte_offset, 4); + assert_eq!(uniform.fields[1].rel_byte_offset, 16); + assert_eq!(packed.fields[1].rel_byte_offset, 4); + } + _ => panic!("Unexpected layout kind"), + } + } + + #[test] + fn test_array_in_struct_field_offset() { + let s = |repr| -> LayoutableType { + SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1), repr) + .extend( + "b", + SizedArray::new( + Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), + NonZeroU32::new(1).unwrap(), + ), + ) // offset 4 for storage and packed, offset 16 for uniform + .into() + }; + + let storage = s(Repr::Storage).layout(); + let uniform = s(Repr::Uniform).layout(); + let packed = s(Repr::Packed).layout(); + + assert_eq!(storage.align(), U32PowerOf2::_4); + assert_eq!(uniform.align(), U32PowerOf2::_16); + assert_eq!(packed.align(), U32PowerOf2::_1); + + assert_eq!(storage.byte_size(), Some(8)); + // field b is bytes 16..=19 and struct size must be a multiple of the struct align (16) + assert_eq!(uniform.byte_size(), Some(32)); + assert_eq!(packed.byte_size(), Some(8)); + + match (storage, uniform, packed) { + (TypeLayout::Struct(storage), TypeLayout::Struct(uniform), TypeLayout::Struct(packed)) => { + assert_eq!(storage.fields[1].rel_byte_offset, 4); + assert_eq!(uniform.fields[1].rel_byte_offset, 16); + assert_eq!(packed.fields[1].rel_byte_offset, 4); + } + _ => panic!("Unexpected layout kind"), + } + } + + #[test] + fn test_unsized_struct_layout() { + let mut unsized_struct = UnsizedStruct { + name: CanonName::from("TestStruct"), + repr: Repr::Storage, + sized_fields: vec![ + SizedField { + name: CanonName::from("field1"), + custom_min_size: None, + custom_min_align: None, + ty: Vector::new(ScalarType::F32, Len::X2).into(), + }, + SizedField { + name: CanonName::from("field2"), + custom_min_size: None, + custom_min_align: None, + ty: Vector::new(ScalarType::F32, Len::X1).into(), + }, + ], + last_unsized: RuntimeSizedArrayField { + name: CanonName::from("dynamic_array"), + custom_min_align: None, + array: RuntimeSizedArray { + element: Vector::new(ScalarType::F32, Len::X1).into(), + }, + }, + }; + let recipe: LayoutableType = unsized_struct.clone().into(); + + let layout = recipe.layout(); + assert_eq!(layout.byte_size(), None); + assert!(layout.align().as_u64() == 8); // align of vec2 + match &layout { + TypeLayout::Struct(struct_layout) => { + assert_eq!(struct_layout.fields.len(), 3); + assert_eq!(struct_layout.fields[0].name, CanonName::from("field1")); + assert_eq!(struct_layout.fields[1].name, CanonName::from("field2")); + assert_eq!(struct_layout.fields[2].name, CanonName::from("dynamic_array")); + + assert_eq!(struct_layout.fields[0].rel_byte_offset, 0); // vec2 + assert_eq!(struct_layout.fields[1].rel_byte_offset, 8); // f32 + assert_eq!(struct_layout.fields[2].rel_byte_offset, 12); // Array + // The last field should be an unsized array + match &struct_layout.fields[2].ty { + TypeLayout::Array(array) => { + assert_eq!(array.byte_size, None); + assert_eq!(array.byte_stride, 4) + } + _ => panic!("Expected runtime-sized array for last field"), + } + } + _ => panic!("Expected structure layout"), + } + + // Testing uniform representation + unsized_struct.repr = Repr::Uniform; + let recipe: LayoutableType = unsized_struct.into(); + println!("{:#?}", recipe); + let layout = recipe.layout(); + assert_eq!(layout.byte_size(), None); + // Struct alignmment has to be a multiple of 16, but the runtime sized array + // also has an alignment of 16, which transfers to the struct alignment. + assert!(layout.align().as_u64() == 16); + match &layout { + TypeLayout::Struct(struct_layout) => { + assert_eq!(struct_layout.fields[0].rel_byte_offset, 0); // vec2 + assert_eq!(struct_layout.fields[1].rel_byte_offset, 8); // f32 + // array has alignment of 16, so offset should be 16 + assert_eq!(struct_layout.fields[2].rel_byte_offset, 16); // Array + match &struct_layout.fields[2].ty { + // Stride has to be a multiple of 16 in uniform address space + TypeLayout::Array(array) => { + assert_eq!(array.byte_size, None); + assert_eq!(array.byte_stride, 16); + } + _ => panic!("Expected runtime-sized array for last field"), + } + } + _ => panic!("Expected structure layout"), + } + } } diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 751e515..5906a07 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -7,7 +7,6 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{dtype_as_scalar_from_f64, ScalarType, ScalarTypeInteger, ScalarTypeNumber}, - type_layout::repr, type_traits::{BindingArgs, GpuAligned, GpuStoreImplCategory, NoAtomics, NoHandles, VertexAttribute}, AsAny, GpuType, To, ToGpuType, }; diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index f2e2182..e0cd3b6 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -507,7 +507,7 @@ impl Display for ArrayStrideAlignmentError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write("", self.ctx.use_color, f)?; + layout.write(f, true)?; writeln!(f); }; writeln!( @@ -542,7 +542,7 @@ impl Display for ArrayStrideError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write("", self.ctx.use_color, f)?; + layout.write(f, true)?; writeln!(f); }; writeln!( @@ -577,7 +577,7 @@ impl Display for ArrayAlignmentError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write("", self.ctx.use_color, f)?; + layout.write(f, true)?; writeln!(f); }; writeln!( diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index 3bec27f..d28b5c0 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -357,20 +357,12 @@ impl WipPushConstantsField { .try_into() .map_err(|e| InternalError::new(true, format!("{e}")))?; let layout = sized_struct.layout(DEFAULT_REPR); - let layout = match &layout.kind { - type_layout::TypeLayoutSemantics::Structure(layout) => &**layout, - _ => unreachable!("expected struct layout for type layout of struct"), - }; let mut ranges = ByteRangesPerStage::default(); for (field, node) in layout.fields.iter().zip(fields.iter().map(|f| f.node)) { let stages = nodes[node].stages.must_appear_in(); - let field_size = field - .field - .ty - .byte_size() - .expect("SizedStruct type enforces Some(size)"); + let field_size = field.ty.byte_size().expect("SizedStruct type enforces Some(size)"); let start = field.rel_byte_offset; let end = start + field_size; diff --git a/shame/src/lib.rs b/shame/src/lib.rs index baf348c..c585be2 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -468,20 +468,16 @@ pub mod any { // type layout pub use type_layout::TypeLayout; - pub use type_layout::GpuTypeLayout; - pub use type_layout::TypeRepr; pub use type_layout::Repr; pub mod repr { use crate::frontend::rust_types::type_layout; - pub use type_layout::repr::Storage; - pub use type_layout::repr::Uniform; - pub use type_layout::repr::Packed; } - pub use type_layout::TypeLayoutSemantics; + pub use type_layout::VectorLayout; + pub use type_layout::PackedVectorLayout; + pub use type_layout::MatrixLayout; + pub use type_layout::ArrayLayout; pub use type_layout::StructLayout; - pub use type_layout::FieldLayoutWithOffset; pub use type_layout::FieldLayout; - pub use type_layout::ElementLayout; // layoutable types pub use type_layout::layoutable::LayoutableType; diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index deb49bb..3d9958f 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -1,454 +1,454 @@ -#![allow(non_camel_case_types, unused)] -use pretty_assertions::{assert_eq, assert_ne}; - -use shame::{self as sm, cpu_layout, gpu_layout}; -use sm::{aliases::*, CpuLayout, GpuLayout}; - -#[test] -fn basic_layout_eq() { - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - b: u32x1, - c: i32x1, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: i32, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[test] -fn attributes_dont_contribute_to_eq() { - #[derive(sm::GpuLayout)] - struct OnGpuA { - a: f32x1, - #[align(4)] // attribute doesn't change layout, u32 is already 4 byte aligned - b: u32x1, - c: i32x1, - } - - #[derive(sm::GpuLayout)] - struct OnGpuB { - a: f32x1, - #[size(4)] // attribute doesn't change layout, u32 is already 4 bytes in size - b: u32x1, - c: i32x1, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: i32, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); - assert_eq!(gpu_layout::(), cpu_layout::()); - assert_eq!(gpu_layout::(), gpu_layout::()); -} - -#[test] -fn fixed_by_align_size_attribute() { - { - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - #[size(32)] - b: f32x3, - c: i32x1, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: f32x3_size32, - c: i32, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); - } - - { - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - b: i32x1, - #[size(32)] - c: f32x3, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: i32, - c: f32x3_size32, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); - } -} - -#[test] -fn different_align_struct_eq() { - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - b: u32x1, - c: i32x1, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: i32, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[test] -fn unsized_struct_layout_eq() { - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - b: u32x1, - c: sm::Array, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: [i32], - } - - assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[derive(Clone, Copy)] -#[repr(C, align(16))] -struct f32x4_cpu(pub [f32; 4]); - -#[derive(Clone, Copy)] -#[repr(C, align(16))] -struct f32x3_cpu(pub [f32; 3]); - -impl CpuLayout for f32x3_cpu { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -} - -#[derive(Clone, Copy)] -#[repr(C, align(8))] -struct f32x2_cpu(pub [f32; 2]); -impl CpuLayout for f32x2_cpu { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -} - -#[derive(Clone, Copy)] -#[repr(C)] -struct f32x2_align4(pub [f32; 2]); -impl CpuLayout for f32x2_align4 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.align = shame::any::U32PowerOf2::_4.into(); - layout - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -struct f32x4_align4(pub [f32; 4]); - -impl CpuLayout for f32x4_align4 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.align = shame::any::U32PowerOf2::_4.into(); - layout - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -struct f32x3_align4(pub [f32; 3]); - -// the tests assume that this is the alignment of glam vecs. -static_assertions::assert_eq_align!(glam::Vec2, f32x2_align4); -static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); -static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); - -impl CpuLayout for f32x3_align4 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.align = shame::any::U32PowerOf2::_4.into(); - layout - } -} - -#[derive(Clone, Copy)] -#[repr(C, align(16))] -struct f32x3_size32(pub [f32; 3], [u8; 20]); - -impl CpuLayout for f32x3_size32 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -} - - - -#[test] -fn unsized_struct_vec3_align_layout_eq() { - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - b: u32x1, - c: sm::Array, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: [f32x3_cpu], - } - - assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[test] -#[rustfmt::skip] fn top_level_align_ignore() { - #[derive(sm::GpuLayout)] - struct OnGpu { // size=16, align=16 - a: f32x4, // size=16, align=16 - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { // size=16, align=4 - a: f32x4_align4, // size=16, align=4 - } - - // the alignment on the top level of the layout doesn't matter. - // two layouts are only considered different if an alignment mismatch - // leads to different offsets of fields or array elements - assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[test] -#[rustfmt::skip] fn struct_align_round_up() { - #[derive(sm::GpuLayout)] - struct OnGpu { // size=round_up(16, 12)=16, align=16 - a: f32x3, // size=12, align=16 - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { // size=12, align=4 - a: f32x3_align4, - } - assert_ne!(gpu_layout::(), cpu_layout::()); - assert!(gpu_layout::().byte_size() == Some(16)); - assert!(gpu_layout::().align().as_u32() == 16); - assert!(cpu_layout::().byte_size() == Some(12)); - assert!(cpu_layout::().align().as_u32() == 4); -} - -#[test] -fn unsized_struct_nested_vec3_align_layout_eq() { - #[derive(sm::GpuLayout)] - struct InnerGpu { - a: f32x1, - b: u32x1, - } - - #[derive(sm::CpuLayout, Clone)] - #[repr(C)] - struct InnerCpu { - a: f32, - b: u32, - } - - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x1, - b: u32x1, - c: sm::Array>, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: [InnerCpu], - } - - assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[test] -fn unsized_array_layout_eq() { - assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); - assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); - assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); - assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); -} - -#[test] -fn layouts_mismatch() { - #[derive(sm::GpuLayout)] - struct OnGpuMore { - a: f32x1, - b: u32x1, - c: i32x1, - d: i32x1, - } - - #[derive(sm::GpuLayout)] - struct OnGpuLess { - a: f32x1, - b: u32x1, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: f32, - b: u32, - c: i32, - } - - assert_ne!(gpu_layout::(), cpu_layout::()); - assert_ne!(gpu_layout::(), cpu_layout::()); -} - -#[test] -fn external_vec_type() { - // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` - pub mod my_mod { - use shame::gpu_layout; - use shame as sm; - use sm::aliases::*; - use sm::GpuLayout as _; - - pub trait CpuLayoutExt { - fn cpu_layout() -> shame::TypeLayout; - } - - impl CpuLayoutExt for glam::Vec4 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } - } - - impl CpuLayoutExt for glam::Vec3 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.align = sm::any::U32PowerOf2::_4.into(); - layout - } - } - } - - #[derive(sm::GpuLayout)] - struct OnGpu { - a: f32x4, - b: f32x4, - } - - use my_mod::CpuLayoutExt as _; // makes `glam::Vec4::layout()` compile in the derive generated code. - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - a: glam::Vec4, - b: glam::Vec4, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); - - #[derive(sm::GpuLayout)] - struct OnGpu2 { - a: f32x3, - b: f32x3, - #[align(16)] - c: f32x4, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu2 { - a: glam::Vec3, - b: glam::Vec3, - c: glam::Vec4, - } - - assert_ne!(gpu_layout::(), cpu_layout::()); - - // TODO: delete or use compile fail test crate like trybuild to make - // sure that align and size attributes aren't allowed on packed structs. - // #[derive(sm::GpuLayout)] - // #[gpu_repr(packed)] - // struct OnGpu2Packed { - // a: f32x3, - // b: f32x3, - // #[align(16)] - // c: f32x4, - // } - - // assert_eq!(gpu_layout::(), cpu_layout::()); -} - -#[test] -#[rustfmt::skip] fn gpu_repr_packed_test() { - { - #[derive(sm::GpuLayout)] - #[gpu_repr(packed)] - struct OnGpu { - pos: f32x3, - nor: f32x3, - uv : f32x2, - } - - #[derive(sm::CpuLayout)] - #[repr(C)] - struct OnCpu { - pos: f32x3_align4, - nor: f32x3_align4, - uv : f32x2_align4, - } - - assert_eq!(gpu_layout::(), cpu_layout::()); - } - { - // TODO: delete or use compile fail test crate like trybuild to make - // sure that align and size attributes aren't allowed on packed structs. - // #[derive(sm::GpuLayout)] - // #[gpu_repr(packed)] - // struct OnGpu { - // pos: f32x3, - // nor: f32x3, - // #[align(8)] uv : f32x2, - // } - - // #[derive(sm::CpuLayout)] - // #[repr(C)] - // struct OnCpu { - // pos: f32x3_align4, - // nor: f32x3_align4, - // uv : f32x2_cpu, - // } - - // assert_eq!(gpu_layout::(), cpu_layout::()); - // enum __ where OnGpu: sm::VertexLayout {} - } -} +// #![allow(non_camel_case_types, unused)] +// use pretty_assertions::{assert_eq, assert_ne}; + +// use shame::{self as sm, cpu_layout, gpu_layout}; +// use sm::{aliases::*, CpuLayout, GpuLayout}; + +// #[test] +// fn basic_layout_eq() { +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// b: u32x1, +// c: i32x1, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: i32, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// fn attributes_dont_contribute_to_eq() { +// #[derive(sm::GpuLayout)] +// struct OnGpuA { +// a: f32x1, +// #[align(4)] // attribute doesn't change layout, u32 is already 4 byte aligned +// b: u32x1, +// c: i32x1, +// } + +// #[derive(sm::GpuLayout)] +// struct OnGpuB { +// a: f32x1, +// #[size(4)] // attribute doesn't change layout, u32 is already 4 bytes in size +// b: u32x1, +// c: i32x1, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: i32, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// assert_eq!(gpu_layout::(), cpu_layout::()); +// assert_eq!(gpu_layout::(), gpu_layout::()); +// } + +// #[test] +// fn fixed_by_align_size_attribute() { +// { +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// #[size(32)] +// b: f32x3, +// c: i32x1, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: f32x3_size32, +// c: i32, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// { +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// b: i32x1, +// #[size(32)] +// c: f32x3, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: i32, +// c: f32x3_size32, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } +// } + +// #[test] +// fn different_align_struct_eq() { +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// b: u32x1, +// c: i32x1, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: i32, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// fn unsized_struct_layout_eq() { +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// b: u32x1, +// c: sm::Array, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: [i32], +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[derive(Clone, Copy)] +// #[repr(C, align(16))] +// struct f32x4_cpu(pub [f32; 4]); + +// #[derive(Clone, Copy)] +// #[repr(C, align(16))] +// struct f32x3_cpu(pub [f32; 3]); + +// impl CpuLayout for f32x3_cpu { +// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +// } + +// #[derive(Clone, Copy)] +// #[repr(C, align(8))] +// struct f32x2_cpu(pub [f32; 2]); +// impl CpuLayout for f32x2_cpu { +// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +// } + +// #[derive(Clone, Copy)] +// #[repr(C)] +// struct f32x2_align4(pub [f32; 2]); +// impl CpuLayout for f32x2_align4 { +// fn cpu_layout() -> shame::TypeLayout { +// let mut layout = gpu_layout::(); +// layout.align = shame::any::U32PowerOf2::_4.into(); +// layout +// } +// } + +// #[derive(Clone, Copy)] +// #[repr(C)] +// struct f32x4_align4(pub [f32; 4]); + +// impl CpuLayout for f32x4_align4 { +// fn cpu_layout() -> shame::TypeLayout { +// let mut layout = gpu_layout::(); +// layout.align = shame::any::U32PowerOf2::_4.into(); +// layout +// } +// } + +// #[derive(Clone, Copy)] +// #[repr(C)] +// struct f32x3_align4(pub [f32; 3]); + +// // the tests assume that this is the alignment of glam vecs. +// static_assertions::assert_eq_align!(glam::Vec2, f32x2_align4); +// static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); +// static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); + +// impl CpuLayout for f32x3_align4 { +// fn cpu_layout() -> shame::TypeLayout { +// let mut layout = gpu_layout::(); +// layout.align = shame::any::U32PowerOf2::_4.into(); +// layout +// } +// } + +// #[derive(Clone, Copy)] +// #[repr(C, align(16))] +// struct f32x3_size32(pub [f32; 3], [u8; 20]); + +// impl CpuLayout for f32x3_size32 { +// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +// } + + + +// #[test] +// fn unsized_struct_vec3_align_layout_eq() { +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// b: u32x1, +// c: sm::Array, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: [f32x3_cpu], +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// #[rustfmt::skip] fn top_level_align_ignore() { +// #[derive(sm::GpuLayout)] +// struct OnGpu { // size=16, align=16 +// a: f32x4, // size=16, align=16 +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { // size=16, align=4 +// a: f32x4_align4, // size=16, align=4 +// } + +// // the alignment on the top level of the layout doesn't matter. +// // two layouts are only considered different if an alignment mismatch +// // leads to different offsets of fields or array elements +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// #[rustfmt::skip] fn struct_align_round_up() { +// #[derive(sm::GpuLayout)] +// struct OnGpu { // size=round_up(16, 12)=16, align=16 +// a: f32x3, // size=12, align=16 +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { // size=12, align=4 +// a: f32x3_align4, +// } +// assert_ne!(gpu_layout::(), cpu_layout::()); +// assert!(gpu_layout::().byte_size() == Some(16)); +// assert!(gpu_layout::().align().as_u32() == 16); +// assert!(cpu_layout::().byte_size() == Some(12)); +// assert!(cpu_layout::().align().as_u32() == 4); +// } + +// #[test] +// fn unsized_struct_nested_vec3_align_layout_eq() { +// #[derive(sm::GpuLayout)] +// struct InnerGpu { +// a: f32x1, +// b: u32x1, +// } + +// #[derive(sm::CpuLayout, Clone)] +// #[repr(C)] +// struct InnerCpu { +// a: f32, +// b: u32, +// } + +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x1, +// b: u32x1, +// c: sm::Array>, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: [InnerCpu], +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// fn unsized_array_layout_eq() { +// assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); +// assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); +// assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); +// assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); +// } + +// #[test] +// fn layouts_mismatch() { +// #[derive(sm::GpuLayout)] +// struct OnGpuMore { +// a: f32x1, +// b: u32x1, +// c: i32x1, +// d: i32x1, +// } + +// #[derive(sm::GpuLayout)] +// struct OnGpuLess { +// a: f32x1, +// b: u32x1, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: f32, +// b: u32, +// c: i32, +// } + +// assert_ne!(gpu_layout::(), cpu_layout::()); +// assert_ne!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// fn external_vec_type() { +// // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` +// pub mod my_mod { +// use shame::gpu_layout; +// use shame as sm; +// use sm::aliases::*; +// use sm::GpuLayout as _; + +// pub trait CpuLayoutExt { +// fn cpu_layout() -> shame::TypeLayout; +// } + +// impl CpuLayoutExt for glam::Vec4 { +// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +// } + +// impl CpuLayoutExt for glam::Vec3 { +// fn cpu_layout() -> shame::TypeLayout { +// let mut layout = gpu_layout::(); +// layout.align = sm::any::U32PowerOf2::_4.into(); +// layout +// } +// } +// } + +// #[derive(sm::GpuLayout)] +// struct OnGpu { +// a: f32x4, +// b: f32x4, +// } + +// use my_mod::CpuLayoutExt as _; // makes `glam::Vec4::layout()` compile in the derive generated code. +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// a: glam::Vec4, +// b: glam::Vec4, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); + +// #[derive(sm::GpuLayout)] +// struct OnGpu2 { +// a: f32x3, +// b: f32x3, +// #[align(16)] +// c: f32x4, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu2 { +// a: glam::Vec3, +// b: glam::Vec3, +// c: glam::Vec4, +// } + +// assert_ne!(gpu_layout::(), cpu_layout::()); + +// // TODO: delete or use compile fail test crate like trybuild to make +// // sure that align and size attributes aren't allowed on packed structs. +// // #[derive(sm::GpuLayout)] +// // #[gpu_repr(packed)] +// // struct OnGpu2Packed { +// // a: f32x3, +// // b: f32x3, +// // #[align(16)] +// // c: f32x4, +// // } + +// // assert_eq!(gpu_layout::(), cpu_layout::()); +// } + +// #[test] +// #[rustfmt::skip] fn gpu_repr_packed_test() { +// { +// #[derive(sm::GpuLayout)] +// #[gpu_repr(packed)] +// struct OnGpu { +// pos: f32x3, +// nor: f32x3, +// uv : f32x2, +// } + +// #[derive(sm::CpuLayout)] +// #[repr(C)] +// struct OnCpu { +// pos: f32x3_align4, +// nor: f32x3_align4, +// uv : f32x2_align4, +// } + +// assert_eq!(gpu_layout::(), cpu_layout::()); +// } +// { +// // TODO: delete or use compile fail test crate like trybuild to make +// // sure that align and size attributes aren't allowed on packed structs. +// // #[derive(sm::GpuLayout)] +// // #[gpu_repr(packed)] +// // struct OnGpu { +// // pos: f32x3, +// // nor: f32x3, +// // #[align(8)] uv : f32x2, +// // } + +// // #[derive(sm::CpuLayout)] +// // #[repr(C)] +// // struct OnCpu { +// // pos: f32x3_align4, +// // nor: f32x3_align4, +// // uv : f32x2_cpu, +// // } + +// // assert_eq!(gpu_layout::(), cpu_layout::()); +// // enum __ where OnGpu: sm::VertexLayout {} +// } +// } diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index a96cf9a..8155208 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -90,8 +90,8 @@ pub fn impl_for_struct( // if no `#[gpu_repr(_)]` attribute was explicitly specified, we default to `Repr::Storage` let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); let gpu_repr_shame = match gpu_repr { - Repr::Packed => quote!( #re::repr::Packed ), - Repr::Storage => quote!( #re::repr::Storage ), + Repr::Packed => quote!( #re::Repr::Packed ), + Repr::Storage => quote!( #re::Repr::Storage ), }; // #[repr(...)] @@ -220,18 +220,6 @@ pub fn impl_for_struct( match which_derive { WhichDerive::GpuLayout => { - let impl_layoutable = quote! { - impl<#generics_decl> #re::Layoutable for #derive_struct_ident<#(#idents_of_generics),*> - where - // These NoBools and NoHandle bounds are only for better diagnostics, Layoutable already implies them - #(#first_fields_type: #re::NoBools + #re::NoHandles + #re::Layoutable + #re::GpuSized,)* - #last_field_type: #re::NoBools + #re::NoHandles + #re::Layoutable, - #where_clause_predicates - { - - } - }; - let impl_gpu_layout = quote! { impl<#generics_decl> #re::GpuLayout for #derive_struct_ident<#(#idents_of_generics),*> where @@ -249,7 +237,7 @@ pub fn impl_for_struct( #field_align.map(|align: u32| TryFrom::try_from(align).expect("power of two validated during codegen")).into(), #field_size.into(), ), - <#field_type as #re::Layoutable>::layoutable_type() + <#field_type as #re::GpuLayout>::layout_recipe() ),)* ], #gpu_repr_shame, @@ -384,7 +372,6 @@ pub fn impl_for_struct( ); Ok(quote! { - #impl_layoutable #impl_gpu_layout #impl_vertex_buffer_layout #impl_fake_auto_traits From 27e89f75a45690af7212ffcafb539de9c7bb629c Mon Sep 17 00:00:00 2001 From: chronicl Date: Tue, 22 Jul 2025 22:54:49 +0200 Subject: [PATCH 138/182] layout comparison / mismatch errors improved --- shame/Cargo.toml | 2 +- shame/src/common/prettify.rs | 11 + shame/src/frontend/rust_types/mod.rs | 12 +- .../rust_types/type_layout/compatible_with.rs | 130 +-- .../src/frontend/rust_types/type_layout/eq.rs | 266 +++-- .../frontend/rust_types/type_layout/mod.rs | 260 ++--- shame/src/ir/ir_type/layout_constraints.rs | 8 +- shame/tests/test_layout.rs | 908 +++++++++--------- 8 files changed, 875 insertions(+), 722 deletions(-) diff --git a/shame/Cargo.toml b/shame/Cargo.toml index f838cf9..6ceb070 100644 --- a/shame/Cargo.toml +++ b/shame/Cargo.toml @@ -26,4 +26,4 @@ shame_derive = { path = "../shame_derive/" } [dev-dependencies] static_assertions = "1.1.0" pretty_assertions = "1.4.1" -glam = "0.29.0" \ No newline at end of file +glam = "0.29.0" diff --git a/shame/src/common/prettify.rs b/shame/src/common/prettify.rs index 719f383..c8eef5e 100644 --- a/shame/src/common/prettify.rs +++ b/shame/src/common/prettify.rs @@ -29,3 +29,14 @@ pub fn set_color(w: &mut W, hexcode: Option<&str>, use_256_c } } } + +/// Implements `Display` to print `Some(T)` as `T` and `None` as the provided &'static str. +pub(crate) struct UnwrapOrStr(pub Option, pub &'static str); +impl std::fmt::Display for UnwrapOrStr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnwrapOrStr(Some(s), _) => s.fmt(f), + UnwrapOrStr(None, s) => s.fmt(f), + } + } +} diff --git a/shame/src/frontend/rust_types/mod.rs b/shame/src/frontend/rust_types/mod.rs index 5d6da72..598a562 100644 --- a/shame/src/frontend/rust_types/mod.rs +++ b/shame/src/frontend/rust_types/mod.rs @@ -62,7 +62,9 @@ pub trait GpuType: ToGpuType + From + AsAny + Clone { /// (no documentation yet) #[track_caller] - fn from_any(any: Any) -> Self { typecheck_downcast(any, Self::ty(), Self::from_any_unchecked) } + fn from_any(any: Any) -> Self { + typecheck_downcast(any, Self::ty(), Self::from_any_unchecked) + } } /// (no documentation yet) @@ -113,11 +115,15 @@ pub trait ToGpuType { /// (no documentation yet) #[track_caller] - fn to_any(&self) -> Any { self.to_gpu().as_any() } + fn to_any(&self) -> Any { + self.to_gpu().as_any() + } /// (no documentation yet) #[track_caller] - fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { None } + fn as_gpu_type_ref(&self) -> Option<&Self::Gpu> { + None + } /// convenience function for [`shame::Cell::new(...)`] /// diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 17a4fd4..b07770f 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -2,8 +2,8 @@ use std::fmt::Write; use crate::{ any::layout::StructLayout, - common::prettify::set_color, - frontend::rust_types::type_layout::{align_to_string, byte_size_to_string, ArrayLayout}, + common::prettify::{set_color, UnwrapOrStr}, + frontend::rust_types::type_layout::{ArrayLayout, LayoutInfo}, ir::ir_type::max_u64_po2_dividing, TypeLayout, }; @@ -170,9 +170,9 @@ impl std::fmt::Display for LayoutError { "Field `{}` in `{}` requires a byte size of {} in {}, but has a byte size of {}", field_left.name, struct_left.name, - byte_size_to_string(field_right.ty.byte_size()), + UnwrapOrStr(field_right.ty.byte_size(), ""), self.address_space, - byte_size_to_string(field_left.ty.byte_size()) + UnwrapOrStr(field_left.ty.byte_size(), "") )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; write_struct(f, struct_left, Some(*field_index), colored)?; @@ -198,7 +198,7 @@ impl std::fmt::Display for LayoutError { writeln!( f, "- add an #[align({})] attribute to the definition of `{}`", - align_to_string(field_right.ty.align()), + field_right.ty.align().as_u32(), field_name )?; writeln!( @@ -224,15 +224,16 @@ impl std::fmt::Display for LayoutError { match self.address_space { (AddressSpaceEnum::WgslUniform | AddressSpaceEnum::WgslStorage) => writeln!( f, - "More info about the {} layout can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + "More info about the {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", self.address_space )?, } } - StructMismatch::FieldCount | StructMismatch::FieldType { .. } => { + StructMismatch::FieldCount | + StructMismatch::FieldName { .. } | + StructMismatch::FieldType { .. } => { unreachable!("{}", types_are_the_same) } - _ => todo!(), }; } } @@ -246,7 +247,7 @@ where { let use_256_color_mode = false; - let mut writer = s.writer(true); + let mut writer = s.writer(LayoutInfo::ALL); writer.writeln_header(f); writer.writeln_struct_declaration(f); for field_index in 0..s.fields.len() { @@ -300,9 +301,14 @@ pub enum TopLevelMismatch { }, } +/// Returns the first mismatch found in the struct fields. +/// +/// Field count is checked last. #[derive(Debug, Clone)] pub enum StructMismatch { - FieldCount, + FieldName { + field_index: usize, + }, FieldType { field_index: usize, }, @@ -317,6 +323,7 @@ pub enum StructMismatch { array_left: ArrayLayout, array_right: ArrayLayout, }, + FieldCount, } /// Find the first depth first layout mismatch @@ -326,7 +333,7 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O // First check if the kinds are the same type match (&layout1, &layout2) { (Vector(v1), Vector(v2)) => { - if v1 != v2 { + if v1.ty != v2.ty { return Some(LayoutMismatch::TopLevel { layout_left: layout1.clone(), layout_right: layout2.clone(), @@ -335,7 +342,7 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } } (PackedVector(p1), PackedVector(p2)) => { - if p1 != p2 { + if p1.ty != p2.ty { return Some(LayoutMismatch::TopLevel { layout_left: layout1.clone(), layout_right: layout2.clone(), @@ -344,7 +351,7 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } } (Matrix(m1), Matrix(m2)) => { - if m1 != m2 { + if m1.ty != m2.ty { return Some(LayoutMismatch::TopLevel { layout_left: layout1.clone(), layout_right: layout2.clone(), @@ -353,27 +360,6 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } } (Array(a1), Array(a2)) => { - // Check array sizes match - if a1.len != a2.len { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::Type, - }); - } - - // Check array stride - if a1.byte_stride != a2.byte_stride { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::ArrayStride { - array_left: (**a1).clone(), - array_right: (**a2).clone(), - }, - }); - } - // Recursively check element types match try_find_mismatch(&a1.element_ty, &a2.element_ty) { // In case the mismatch isn't related to a struct field mismatch, @@ -398,6 +384,27 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O m @ Some(LayoutMismatch::Struct { .. }) => return m, None => return None, } + + // Check array sizes match + if a1.len != a2.len { + return Some(LayoutMismatch::TopLevel { + layout_left: layout1.clone(), + layout_right: layout2.clone(), + mismatch: TopLevelMismatch::Type, + }); + } + + // Check array stride + if a1.byte_stride != a2.byte_stride { + return Some(LayoutMismatch::TopLevel { + layout_left: layout1.clone(), + layout_right: layout2.clone(), + mismatch: TopLevelMismatch::ArrayStride { + array_left: (**a1).clone(), + array_right: (**a2).clone(), + }, + }); + } } (Struct(s1), Struct(s2)) => { return try_find_struct_mismatch(s1, s2); @@ -416,31 +423,17 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> Option { - // Check field count - if struct1.fields.len() != struct2.fields.len() { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldCount, - }); - } - for (field_index, (field1, field2)) in struct1.fields.iter().zip(struct2.fields.iter()).enumerate() { - // Check field offset - if field1.rel_byte_offset != field2.rel_byte_offset { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldOffset { field_index }, - }); - } - - // Check field byte size - if field1.ty.byte_size() != field2.ty.byte_size() { + // Order of checks is important here. We check in order + // - field name + // - field type and if the field is/contains a struct, recursively check its fields + // - field offset + // - field byte size + if field1.name != field2.name { return Some(LayoutMismatch::Struct { struct_left: struct1.clone(), struct_right: struct2.clone(), - mismatch: StructMismatch::FieldByteSize { field_index }, + mismatch: StructMismatch::FieldName { field_index }, }); } @@ -480,6 +473,33 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O struct_mismatch @ LayoutMismatch::Struct { .. } => return Some(struct_mismatch), } } + + // Check field offset + if field1.rel_byte_offset != field2.rel_byte_offset { + return Some(LayoutMismatch::Struct { + struct_left: struct1.clone(), + struct_right: struct2.clone(), + mismatch: StructMismatch::FieldOffset { field_index }, + }); + } + + // Check field byte size + if field1.ty.byte_size() != field2.ty.byte_size() { + return Some(LayoutMismatch::Struct { + struct_left: struct1.clone(), + struct_right: struct2.clone(), + mismatch: StructMismatch::FieldByteSize { field_index }, + }); + } + } + + // Check field count + if struct1.fields.len() != struct2.fields.len() { + return Some(LayoutMismatch::Struct { + struct_left: struct1.clone(), + struct_right: struct2.clone(), + mismatch: StructMismatch::FieldCount, + }); } None diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index abe72b9..c13c906 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,4 +1,4 @@ -use crate::frontend::rust_types::type_layout::compatible_with::TopLevelMismatch; +use crate::frontend::rust_types::type_layout::compatible_with::{StructMismatch, TopLevelMismatch}; use super::compatible_with::try_find_mismatch; use super::*; @@ -53,9 +53,20 @@ impl LayoutMismatch { colored: bool, ) -> Result { let tab = " "; - // TODO(chronicl) include names somehow let [(a_name, a), (b_name, b)] = layouts; + let use_256_color_mode = false; + let enable_color = |f_: &mut W, hex| match colored { + true => set_color(f_, Some(hex), use_256_color_mode), + false => Ok(()), + }; + let reset_color = |f_: &mut W| match colored { + true => set_color(f_, None, use_256_color_mode), + false => Ok(()), + }; + let hex_left = "#DF5853"; // red + let hex_right = "#9A639C"; // purple + // Using try_find_mismatch to find the first mismatching type / struct field. let Some(mismatch) = try_find_mismatch(a, b) else { return Ok(Mismatch::NotFound); @@ -107,47 +118,131 @@ impl LayoutMismatch { struct_right, mismatch, } => { - let use_256_color_mode = false; - let enable_color = |f_: &mut W, hex| match colored { - true => set_color(f_, Some(hex), use_256_color_mode), - false => Ok(()), + let field_name = |field_index: usize| { + if let Some(name) = struct_left.fields.get(field_index).map(|f| &f.name) { + format!("field `{name}`") + } else { + // Should never happen, but just in case we fall back to this + format!("field {field_index}") + } }; - let reset_color = |f_: &mut W| match colored { - true => set_color(f_, None, use_256_color_mode), - false => Ok(()), + + writeln!( + f, + "The layouts of `{}` and `{}` do not match, because the", + struct_left.name, struct_right.name + ); + let (mismatch_field_index, layout_info) = match mismatch { + StructMismatch::FieldName { field_index } => { + writeln!(f, "names of field {field_index} are different.")?; + (Some(field_index), LayoutInfo::NONE) + } + StructMismatch::FieldType { field_index } => { + writeln!(f, "type of {} is different.", field_name(field_index))?; + (Some(field_index), LayoutInfo::NONE) + } + StructMismatch::FieldByteSize { field_index } => { + writeln!(f, "byte size of {} is different.", field_name(field_index))?; + (Some(field_index), LayoutInfo::SIZE) + } + StructMismatch::FieldOffset { field_index } => { + writeln!(f, "offset of {} is different.", field_name(field_index))?; + ( + Some(field_index), + LayoutInfo::OFFSET | LayoutInfo::ALIGN | LayoutInfo::SIZE, + ) + } + StructMismatch::FieldArrayStride { field_index, .. } => { + writeln!(f, "array stride of {} is different.", field_name(field_index))?; + (Some(field_index), LayoutInfo::STRIDE) + } + StructMismatch::FieldCount => { + writeln!(f, "number of fields is different.")?; + (None, LayoutInfo::NONE) + } + }; + writeln!(f)?; + + let fields_without_mismatch = match mismatch_field_index { + Some(index) => struct_left.fields.len().min(struct_right.fields.len()).min(index), + None => struct_left.fields.len().min(struct_right.fields.len()), }; - let hex_left = "#DF5853"; // red - let hex_right = "#9A639C"; // purple - let mut writer_left = struct_left.writer(true); - let mut writer_right = struct_right.writer(true); - // Making sure that both writer have the same layout info offset, the max of both. - writer_left.ensure_layout_info_offset(writer_right.layout_info_offset()); - writer_right.ensure_layout_info_offset(writer_left.layout_info_offset()); + // Start writing the structs with the mismatch highlighted + let mut writer_left = struct_left.writer(layout_info); + let mut writer_right = struct_right.writer(layout_info); + + // Make sure layout info offset only takes account the fields before and including the mismatch, + // because those are the only fields that will be written below. + if let Some(mismatch_field_index) = mismatch_field_index { + let max_fields = Some(mismatch_field_index + 1); + writer_left.set_layout_info_offset_auto(max_fields); + writer_right.set_layout_info_offset_auto(max_fields); + } + // Make sure layout info offset is large enough to fit the custom struct declaration + let struct_declaration = format!("struct {a_name} / {b_name} {{"); + let layout_info_offset = writer_left + .layout_info_offset() + .max(writer_right.layout_info_offset()) + .max(struct_declaration.len()); + writer_left.ensure_layout_info_offset(layout_info_offset); + writer_right.ensure_layout_info_offset(layout_info_offset); + // Write header writer_left.writeln_header(f)?; + + // Write custom struct declaration + write!(f, "struct ")?; enable_color(f, hex_left)?; - writer_left.writeln_struct_declaration(f)?; + write!(f, "{}", struct_left.name)?; reset_color(f)?; + write!(f, " / ")?; enable_color(f, hex_right)?; - writer_right.writeln_struct_declaration(f)?; + write!(f, "{}", struct_right.name)?; reset_color(f)?; - for field_index in 0..struct_left.fields.len() { - let field_left = &struct_left.fields[field_index]; - let field_right = &struct_right.fields[field_index]; + writeln!(f, " {{")?; - // We are checking every field for equality, instead of using the singular found mismatch from try_find_mismatch - if field_left.ty == field_right.ty && field_left.rel_byte_offset == field_right.rel_byte_offset { - writer_left.writeln_field(f, field_index)?; - } else { + // Write matching fields + for field_index in 0..fields_without_mismatch { + writer_left.writeln_field(f, field_index)?; + } + + match mismatch { + StructMismatch::FieldName { field_index } | + StructMismatch::FieldType { field_index } | + StructMismatch::FieldByteSize { field_index } | + StructMismatch::FieldOffset { field_index } | + StructMismatch::FieldArrayStride { field_index, .. } => { + // Write mismatching field enable_color(f, hex_left)?; - writer_left.writeln_field(f, field_index)?; + writer_left.write_field(f, field_index)?; + writeln!(f, " <-- {a_name}")?; reset_color(f)?; enable_color(f, hex_right)?; - writer_right.writeln_field(f, field_index)?; + writer_right.write_field(f, field_index)?; + writeln!(f, " <-- {b_name}")?; + reset_color(f)?; + if struct_left.fields.len() > field_index + 1 || struct_right.fields.len() > field_index + 1 { + // Write ellipsis if there are more fields after the mismatch + writeln!(f, "{}...", writer_left.tab())?; + } + } + StructMismatch::FieldCount => { + // Write the remaining fields of the larger struct + let (writer, len, hex) = match struct_left.fields.len() > struct_right.fields.len() { + true => (&mut writer_left, struct_left.fields.len(), hex_left), + false => (&mut writer_right, struct_right.fields.len(), hex_right), + }; + + enable_color(f, hex)?; + for field_index in fields_without_mismatch..len { + writer.writeln_field(f, field_index)?; + } reset_color(f)?; } } + + // Write closing bracket writer_left.writeln_struct_end(f)?; } } @@ -174,75 +269,74 @@ where } } -#[test] -fn test_layout_mismatch() { +#[cfg(test)] +mod tests { use crate as shame; use shame as sm; - use shame::CpuLayout; + use shame::{CpuLayout, GpuLayout, gpu_layout, cpu_layout}; use crate::aliases::*; - #[derive(shame::GpuLayout)] - #[cpu(ACpu)] - pub struct A { - a: u32x1, - c: sm::Array>, - b: f32x1, - } - - #[derive(shame::CpuLayout)] + #[derive(Clone, Copy)] #[repr(C)] - pub struct ACpu { - d: u32, - c: [[f32; 3]; 2], - b: u32, + struct f32x3_align4(pub [f32; 4]); + + impl CpuLayout for f32x3_align4 { + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.set_align(shame::any::U32PowerOf2::_4); + layout + } } - let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); - let mut drawcall = encoder.new_render_pipeline(sm::Indexing::BufferU16); - let mut group0 = drawcall.bind_groups.next(); - let a: sm::Buffer = group0.next(); - - let primitive = drawcall - .vertices - .assemble(f32x3::zero(), sm::Draw::triangle_list(sm::Winding::Ccw)); - let frag = primitive.rasterize(sm::Accuracy::Relaxed); - frag.fill(f32x3::zero()); - encoder.finish().unwrap(); -} + fn print_mismatch() { + println!( + "{}", + super::check_eq(("gpu", &gpu_layout::()), ("cpu", &cpu_layout::())).unwrap_err() + ); + } + #[test] + fn test_layout_mismatch_nested() { + // For colored output + let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); + let mut drawcall = encoder.new_render_pipeline(sm::Indexing::BufferU16); -#[test] -fn test_layout_mismatch_nested() { - use crate as shame; - use shame as sm; - use shame::CpuLayout; - use crate::aliases::*; + // field name mismatch + #[derive(GpuLayout)] + pub struct A { + a: u32x1, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct ACpu { + b: u32, + } + print_mismatch::(); - #[derive(shame::GpuLayout)] - #[cpu(ACpu)] - pub struct A { - a: u32x1, - c: sm::Array>, - b: f32x1, - } + // field type mismatch + #[derive(GpuLayout)] + pub struct B { + a: f32x1, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct BCpu { + a: u32, + } + print_mismatch::(); - #[derive(shame::CpuLayout)] - #[repr(C)] - pub struct ACpu { - d: u32, - c: [[f32; 3]; 2], - b: u32, + // field offset mismatch + #[derive(GpuLayout)] + pub struct C { + a: f32x1, + b: f32x3, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct CCpu { + a: f32, + b: f32x3_align4, + } + print_mismatch::(); } - - let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); - let mut drawcall = encoder.new_render_pipeline(sm::Indexing::BufferU16); - let mut group0 = drawcall.bind_groups.next(); - let a: sm::Buffer = group0.next(); - - let primitive = drawcall - .vertices - .assemble(f32x3::zero(), sm::Draw::triangle_list(sm::Winding::Ccw)); - let frag = primitive.rasterize(sm::Accuracy::Relaxed); - frag.fill(f32x3::zero()); - encoder.finish().unwrap(); } diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 8e30e8b..eec4771 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -9,9 +9,12 @@ use std::{ }; use crate::{ - any::{U32PowerOf2}, + any::U32PowerOf2, call_info, - common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, + common::{ + ignore_eq::IgnoreInEqOrdHash, + prettify::{set_color, UnwrapOrStr}, + }, ir::{ self, ir_type::{round_up, CanonName}, @@ -148,46 +151,99 @@ impl ArrayLayout { } } -pub(crate) fn align_to_string(align: U32PowerOf2) -> String { align.as_u32().to_string() } -pub(crate) fn byte_size_to_string(size: Option) -> String { size.map(|s| s.to_string()).unwrap_or("None".into()) } -fn write_indent(mut f: &mut W, n: usize) -> std::fmt::Result { - for _ in 0..n { - f.write_char(' ')? +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LayoutInfo(u8); +#[rustfmt::skip] +impl LayoutInfo { + pub const NONE: Self = Self(0); + pub const OFFSET: Self = Self(1 << 0); + pub const ALIGN: Self = Self(1 << 1); + pub const SIZE: Self = Self(1 << 2); + pub const STRIDE: Self = Self(1 << 3); + pub const ALL: Self = Self(Self::OFFSET.0 | Self::ALIGN.0 | Self::SIZE.0 | Self::STRIDE.0); +} +impl std::ops::BitOr for LayoutInfo { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { LayoutInfo(self.0 | rhs.0) } +} +impl LayoutInfo { + pub fn contains(&self, other: Self) -> bool { (self.0 & other.0) == other.0 } + + pub fn header(&self) -> String { + let mut parts = Vec::with_capacity(4); + for (info, info_str) in [ + (Self::OFFSET, "offset"), + (Self::ALIGN, "align"), + (Self::SIZE, "size"), + (Self::STRIDE, "stride"), + ] { + if self.contains(info) { + parts.push(info_str); + } + } + parts.join(" ") + } + + pub fn format(&self, offset: Option, align: U32PowerOf2, size: Option, stride: Option) -> String { + let infos: [(Self, &'static str, &dyn Display); 4] = [ + (Self::OFFSET, "offset", &UnwrapOrStr(offset, "")), + (Self::ALIGN, "align", &align.as_u32()), + (Self::SIZE, "size", &UnwrapOrStr(size, "")), + (Self::STRIDE, "stride", &UnwrapOrStr(stride, "")), + ]; + let mut parts = Vec::with_capacity(4); + for (info, info_str, value) in infos { + if self.contains(info) { + parts.push(format!("{:>info_width$}", value, info_width = info_str.len())); + } + } + parts.join(" ") } - Ok(()) } pub struct StructWriter<'a> { s: &'a StructLayout, tab: &'static str, - include_layout_info: bool, + layout_info: LayoutInfo, layout_info_offset: usize, } impl<'a> StructWriter<'a> { - pub fn new(s: &'a StructLayout, include_layout_info: bool) -> Self { + pub fn new(s: &'a StructLayout, layout_info: LayoutInfo) -> Self { let mut this = Self { s, - tab: " ", - include_layout_info, + tab: " ", + layout_info, layout_info_offset: 0, }; - let layout_info_offset = this.struct_declaration().len().max( - (0..this.s.fields.len()) - .map(|i| this.field_declaration(i).len()) - .max() - .unwrap_or(0), - ); - this.ensure_layout_info_offset(layout_info_offset); + this.set_layout_info_offset_auto(None); this } pub(crate) fn layout_info_offset(&self) -> usize { self.layout_info_offset } + /// By setting `max_fields` to `Some(n)`, the writer will adjust Self::layout_info_offset + /// to only take into account the first `n` fields of the struct. + pub(crate) fn set_layout_info_offset_auto(&mut self, max_fields: Option) { + let fields = match max_fields { + Some(n) => n.min(self.s.fields.len()), + None => self.s.fields.len(), + }; + let layout_info_offset = (0..fields) + .map(|i| self.field_declaration(i).len()) + .max() + .unwrap_or(0) + .max(self.struct_declaration().len()); + self.layout_info_offset = layout_info_offset; + } + pub(crate) fn ensure_layout_info_offset(&mut self, min_layout_info_offset: usize) { self.layout_info_offset = self.layout_info_offset.max(min_layout_info_offset) } + pub(crate) fn tab(&self) -> &'static str { self.tab } + fn struct_declaration(&self) -> String { format!("struct {} {{", self.s.name) } fn field_declaration(&self, field_index: usize) -> String { @@ -197,9 +253,50 @@ impl<'a> StructWriter<'a> { } } + pub(crate) fn write_header(&self, f: &mut W) -> std::fmt::Result { + if self.layout_info != LayoutInfo::NONE { + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{}", "", self.layout_info.header())?; + } + Ok(()) + } + + pub(crate) fn write_struct_declaration(&self, f: &mut W) -> std::fmt::Result { + let info = self.layout_info.format( + None, // offset is not applicable for structs + *self.s.align, + self.s.byte_size, + None, // stride is not applicable for structs + ); + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{info}", self.struct_declaration()) + } + + pub(crate) fn write_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { + use TypeLayout::*; + + let field = &self.s.fields[field_index]; + let info = self.layout_info.format( + Some(field.rel_byte_offset), + field.ty.align(), + field.ty.byte_size(), + match &field.ty { + Array(array) => Some(array.byte_stride), + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, + }, + ); + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{info}", self.field_declaration(field_index)) + } + + pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } + pub(crate) fn writeln_header(&self, f: &mut W) -> std::fmt::Result { - self.write_header(f)?; - writeln!(f) + if self.layout_info != LayoutInfo::NONE { + self.write_header(f)?; + writeln!(f)?; + } + Ok(()) } pub(crate) fn writeln_struct_declaration(&self, f: &mut W) -> std::fmt::Result { @@ -216,89 +313,23 @@ impl<'a> StructWriter<'a> { self.write_struct_end(f)?; writeln!(f) } - - pub(crate) fn write_header(&self, f: &mut W) -> std::fmt::Result { - use TypeLayout::*; - let has_array_field = self.s.fields.iter().any(|f| match f.ty { - Array(_) => true, - Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => false, - }); - let layout_info = if has_array_field { - "offset align size stride" - } else { - "offset align size" - }; - if self.include_layout_info { - write!( - f, - "{:info_offset$} {layout_info}", - "", - info_offset = self.layout_info_offset() - ) - } else { - Ok(()) - } - } - - pub(crate) fn write_struct_declaration(&self, f: &mut W) -> std::fmt::Result { - if self.include_layout_info { - write!( - f, - "{:info_offset$} {:>6} {:>5} {:>4}", - self.struct_declaration(), - "", - align_to_string(*self.s.align), - byte_size_to_string(self.s.byte_size), - info_offset = self.layout_info_offset() - ) - } else { - write!(f, "{}", self.struct_declaration()) - } - } - - pub(crate) fn write_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { - let field = &self.s.fields[field_index]; - if self.include_layout_info { - write!( - f, - "{:info_offset$} {:>6} {:>5} {:>4}", - self.field_declaration(field_index), - field.rel_byte_offset, - align_to_string(field.ty.align()), - byte_size_to_string(field.ty.byte_size()), - info_offset = self.layout_info_offset() - )?; - match &field.ty { - TypeLayout::Array(a) => write!(f, " {:>5}", a.byte_stride), - TypeLayout::Vector(_) | TypeLayout::PackedVector(_) | TypeLayout::Matrix(_) | TypeLayout::Struct(_) => { - Ok(()) - } - } - } else { - write!(f, "{}", self.field_declaration(field_index)) - } - } - - pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } } impl StructLayout { pub fn short_name(&self) -> String { self.name.to_string() } - pub(crate) fn to_string_with_layout_information(&self) -> Result { + pub(crate) fn to_string_with_layout_info(&self, layout_info: LayoutInfo) -> Result { let mut s = String::new(); - self.write(&mut s, true)?; + self.write(&mut s, layout_info)?; Ok(s) } - pub(crate) fn writer(&self, include_layout_info: bool) -> StructWriter<'_> { - StructWriter::new(self, include_layout_info) - } + pub(crate) fn writer(&self, layout_info: LayoutInfo) -> StructWriter<'_> { StructWriter::new(self, layout_info) } - pub(crate) fn write(&self, f: &mut W, include_layout_info: bool) -> std::fmt::Result { + pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { use TypeLayout::*; - let mut writer = self.writer(include_layout_info); + let mut writer = self.writer(layout_info); writer.writeln_header(f)?; writer.writeln_struct_declaration(f)?; for i in 0..self.fields.len() { @@ -403,44 +434,35 @@ impl TypeLayout { } } - pub(crate) fn to_string_with_layout_information(&self) -> Result { + pub(crate) fn to_string_with_layout_information(&self, layout_info: LayoutInfo) -> Result { let mut s = String::new(); - self.write(&mut s, true)?; + self.write(&mut s, layout_info)?; Ok(s) } - pub(crate) fn write(&self, f: &mut W, include_layout_info: bool) -> std::fmt::Result { + pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { use TypeLayout::*; - let align = |layout: &TypeLayout| align_to_string(layout.align()); - let size = |layout: &TypeLayout| byte_size_to_string(layout.byte_size()); - - let (stride, header) = match self { - Array(a) => (Some(a.byte_stride), "align size stride"), - Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => (None, "align size"), - }; match self { Vector(_) | PackedVector(_) | Matrix(_) | Array(_) => { let plain = self.short_name(); - if include_layout_info { - // Write header - write_indent(f, plain.len() + 1)?; - f.write_str(header)?; - writeln!(f); - - // Write type and layout info - write!(f, "{plain} {:5} {:4}", align(self), size(self))?; - if let Some(stride) = stride { - writeln!(f, " {stride}")?; - } else { - writeln!(f)? - } - } else { - writeln!(f, "{plain}")? + let stride = match self { + Array(a) => Some(a.byte_stride), + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, + }; + let info_offset = plain.len() + 1; + + // Write header if some layout information is requested + if layout_info != LayoutInfo::NONE { + writeln!(f, "{:info_offset$}{}", "", layout_info.header())?; } + + // Write the type name and layout information + let info = layout_info.format(None, self.align(), self.byte_size(), stride); + writeln!(f, "{plain:info_offset$}{info}")?; } - Struct(s) => s.write(f, include_layout_info)?, + Struct(s) => s.write(f, layout_info)?, }; Ok(()) @@ -484,7 +506,7 @@ impl From for TypeLayout { impl Display for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, true) } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, LayoutInfo::ALL) } } diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index e0cd3b6..692241d 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -6,7 +6,7 @@ use std::{ use thiserror::Error; -use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, TypeLayout}; +use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, LayoutInfo, TypeLayout}; use crate::{ backend::language::Language, call_info, @@ -507,7 +507,7 @@ impl Display for ArrayStrideAlignmentError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write(f, true)?; + layout.write(f, LayoutInfo::ALL)?; writeln!(f); }; writeln!( @@ -542,7 +542,7 @@ impl Display for ArrayStrideError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write(f, true)?; + layout.write(f, LayoutInfo::ALL)?; writeln!(f); }; writeln!( @@ -577,7 +577,7 @@ impl Display for ArrayAlignmentError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write(f, true)?; + layout.write(f, LayoutInfo::ALL)?; writeln!(f); }; writeln!( diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 3d9958f..7f1f589 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -1,454 +1,454 @@ -// #![allow(non_camel_case_types, unused)] -// use pretty_assertions::{assert_eq, assert_ne}; - -// use shame::{self as sm, cpu_layout, gpu_layout}; -// use sm::{aliases::*, CpuLayout, GpuLayout}; - -// #[test] -// fn basic_layout_eq() { -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// b: u32x1, -// c: i32x1, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: i32, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// fn attributes_dont_contribute_to_eq() { -// #[derive(sm::GpuLayout)] -// struct OnGpuA { -// a: f32x1, -// #[align(4)] // attribute doesn't change layout, u32 is already 4 byte aligned -// b: u32x1, -// c: i32x1, -// } - -// #[derive(sm::GpuLayout)] -// struct OnGpuB { -// a: f32x1, -// #[size(4)] // attribute doesn't change layout, u32 is already 4 bytes in size -// b: u32x1, -// c: i32x1, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: i32, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// assert_eq!(gpu_layout::(), cpu_layout::()); -// assert_eq!(gpu_layout::(), gpu_layout::()); -// } - -// #[test] -// fn fixed_by_align_size_attribute() { -// { -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// #[size(32)] -// b: f32x3, -// c: i32x1, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: f32x3_size32, -// c: i32, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// { -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// b: i32x1, -// #[size(32)] -// c: f32x3, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: i32, -// c: f32x3_size32, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } -// } - -// #[test] -// fn different_align_struct_eq() { -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// b: u32x1, -// c: i32x1, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: i32, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// fn unsized_struct_layout_eq() { -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// b: u32x1, -// c: sm::Array, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: [i32], -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[derive(Clone, Copy)] -// #[repr(C, align(16))] -// struct f32x4_cpu(pub [f32; 4]); - -// #[derive(Clone, Copy)] -// #[repr(C, align(16))] -// struct f32x3_cpu(pub [f32; 3]); - -// impl CpuLayout for f32x3_cpu { -// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -// } - -// #[derive(Clone, Copy)] -// #[repr(C, align(8))] -// struct f32x2_cpu(pub [f32; 2]); -// impl CpuLayout for f32x2_cpu { -// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -// } - -// #[derive(Clone, Copy)] -// #[repr(C)] -// struct f32x2_align4(pub [f32; 2]); -// impl CpuLayout for f32x2_align4 { -// fn cpu_layout() -> shame::TypeLayout { -// let mut layout = gpu_layout::(); -// layout.align = shame::any::U32PowerOf2::_4.into(); -// layout -// } -// } - -// #[derive(Clone, Copy)] -// #[repr(C)] -// struct f32x4_align4(pub [f32; 4]); - -// impl CpuLayout for f32x4_align4 { -// fn cpu_layout() -> shame::TypeLayout { -// let mut layout = gpu_layout::(); -// layout.align = shame::any::U32PowerOf2::_4.into(); -// layout -// } -// } - -// #[derive(Clone, Copy)] -// #[repr(C)] -// struct f32x3_align4(pub [f32; 3]); - -// // the tests assume that this is the alignment of glam vecs. -// static_assertions::assert_eq_align!(glam::Vec2, f32x2_align4); -// static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); -// static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); - -// impl CpuLayout for f32x3_align4 { -// fn cpu_layout() -> shame::TypeLayout { -// let mut layout = gpu_layout::(); -// layout.align = shame::any::U32PowerOf2::_4.into(); -// layout -// } -// } - -// #[derive(Clone, Copy)] -// #[repr(C, align(16))] -// struct f32x3_size32(pub [f32; 3], [u8; 20]); - -// impl CpuLayout for f32x3_size32 { -// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -// } - - - -// #[test] -// fn unsized_struct_vec3_align_layout_eq() { -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// b: u32x1, -// c: sm::Array, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: [f32x3_cpu], -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// #[rustfmt::skip] fn top_level_align_ignore() { -// #[derive(sm::GpuLayout)] -// struct OnGpu { // size=16, align=16 -// a: f32x4, // size=16, align=16 -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { // size=16, align=4 -// a: f32x4_align4, // size=16, align=4 -// } - -// // the alignment on the top level of the layout doesn't matter. -// // two layouts are only considered different if an alignment mismatch -// // leads to different offsets of fields or array elements -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// #[rustfmt::skip] fn struct_align_round_up() { -// #[derive(sm::GpuLayout)] -// struct OnGpu { // size=round_up(16, 12)=16, align=16 -// a: f32x3, // size=12, align=16 -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { // size=12, align=4 -// a: f32x3_align4, -// } -// assert_ne!(gpu_layout::(), cpu_layout::()); -// assert!(gpu_layout::().byte_size() == Some(16)); -// assert!(gpu_layout::().align().as_u32() == 16); -// assert!(cpu_layout::().byte_size() == Some(12)); -// assert!(cpu_layout::().align().as_u32() == 4); -// } - -// #[test] -// fn unsized_struct_nested_vec3_align_layout_eq() { -// #[derive(sm::GpuLayout)] -// struct InnerGpu { -// a: f32x1, -// b: u32x1, -// } - -// #[derive(sm::CpuLayout, Clone)] -// #[repr(C)] -// struct InnerCpu { -// a: f32, -// b: u32, -// } - -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x1, -// b: u32x1, -// c: sm::Array>, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: [InnerCpu], -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// fn unsized_array_layout_eq() { -// assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); -// assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); -// assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); -// assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); -// } - -// #[test] -// fn layouts_mismatch() { -// #[derive(sm::GpuLayout)] -// struct OnGpuMore { -// a: f32x1, -// b: u32x1, -// c: i32x1, -// d: i32x1, -// } - -// #[derive(sm::GpuLayout)] -// struct OnGpuLess { -// a: f32x1, -// b: u32x1, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: f32, -// b: u32, -// c: i32, -// } - -// assert_ne!(gpu_layout::(), cpu_layout::()); -// assert_ne!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// fn external_vec_type() { -// // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` -// pub mod my_mod { -// use shame::gpu_layout; -// use shame as sm; -// use sm::aliases::*; -// use sm::GpuLayout as _; - -// pub trait CpuLayoutExt { -// fn cpu_layout() -> shame::TypeLayout; -// } - -// impl CpuLayoutExt for glam::Vec4 { -// fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } -// } - -// impl CpuLayoutExt for glam::Vec3 { -// fn cpu_layout() -> shame::TypeLayout { -// let mut layout = gpu_layout::(); -// layout.align = sm::any::U32PowerOf2::_4.into(); -// layout -// } -// } -// } - -// #[derive(sm::GpuLayout)] -// struct OnGpu { -// a: f32x4, -// b: f32x4, -// } - -// use my_mod::CpuLayoutExt as _; // makes `glam::Vec4::layout()` compile in the derive generated code. -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// a: glam::Vec4, -// b: glam::Vec4, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); - -// #[derive(sm::GpuLayout)] -// struct OnGpu2 { -// a: f32x3, -// b: f32x3, -// #[align(16)] -// c: f32x4, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu2 { -// a: glam::Vec3, -// b: glam::Vec3, -// c: glam::Vec4, -// } - -// assert_ne!(gpu_layout::(), cpu_layout::()); - -// // TODO: delete or use compile fail test crate like trybuild to make -// // sure that align and size attributes aren't allowed on packed structs. -// // #[derive(sm::GpuLayout)] -// // #[gpu_repr(packed)] -// // struct OnGpu2Packed { -// // a: f32x3, -// // b: f32x3, -// // #[align(16)] -// // c: f32x4, -// // } - -// // assert_eq!(gpu_layout::(), cpu_layout::()); -// } - -// #[test] -// #[rustfmt::skip] fn gpu_repr_packed_test() { -// { -// #[derive(sm::GpuLayout)] -// #[gpu_repr(packed)] -// struct OnGpu { -// pos: f32x3, -// nor: f32x3, -// uv : f32x2, -// } - -// #[derive(sm::CpuLayout)] -// #[repr(C)] -// struct OnCpu { -// pos: f32x3_align4, -// nor: f32x3_align4, -// uv : f32x2_align4, -// } - -// assert_eq!(gpu_layout::(), cpu_layout::()); -// } -// { -// // TODO: delete or use compile fail test crate like trybuild to make -// // sure that align and size attributes aren't allowed on packed structs. -// // #[derive(sm::GpuLayout)] -// // #[gpu_repr(packed)] -// // struct OnGpu { -// // pos: f32x3, -// // nor: f32x3, -// // #[align(8)] uv : f32x2, -// // } - -// // #[derive(sm::CpuLayout)] -// // #[repr(C)] -// // struct OnCpu { -// // pos: f32x3_align4, -// // nor: f32x3_align4, -// // uv : f32x2_cpu, -// // } - -// // assert_eq!(gpu_layout::(), cpu_layout::()); -// // enum __ where OnGpu: sm::VertexLayout {} -// } -// } +#![allow(non_camel_case_types, unused)] +use pretty_assertions::{assert_eq, assert_ne}; + +use shame::{self as sm, cpu_layout, gpu_layout}; +use sm::{aliases::*, CpuLayout, GpuLayout}; + +#[test] +fn basic_layout_eq() { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: u32x1, + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: i32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[test] +fn attributes_dont_contribute_to_eq() { + #[derive(sm::GpuLayout)] + struct OnGpuA { + a: f32x1, + #[align(4)] // attribute doesn't change layout, u32 is already 4 byte aligned + b: u32x1, + c: i32x1, + } + + #[derive(sm::GpuLayout)] + struct OnGpuB { + a: f32x1, + #[size(4)] // attribute doesn't change layout, u32 is already 4 bytes in size + b: u32x1, + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: i32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), cpu_layout::()); + assert_eq!(gpu_layout::(), gpu_layout::()); +} + +#[test] +fn fixed_by_align_size_attribute() { + { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + #[size(32)] + b: f32x3, + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: f32x3_size32, + c: i32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + } + + { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: i32x1, + #[size(32)] + c: f32x3, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: i32, + c: f32x3_size32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + } +} + +#[test] +fn different_align_struct_eq() { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: u32x1, + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: i32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[test] +fn unsized_struct_layout_eq() { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: u32x1, + c: sm::Array, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: [i32], + } + + assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[derive(Clone, Copy)] +#[repr(C, align(16))] +struct f32x4_cpu(pub [f32; 4]); + +#[derive(Clone, Copy)] +#[repr(C, align(16))] +struct f32x3_cpu(pub [f32; 3]); + +impl CpuLayout for f32x3_cpu { + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +} + +#[derive(Clone, Copy)] +#[repr(C, align(8))] +struct f32x2_cpu(pub [f32; 2]); +impl CpuLayout for f32x2_cpu { + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct f32x2_align4(pub [f32; 2]); +impl CpuLayout for f32x2_align4 { + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.set_align(shame::any::U32PowerOf2::_4); + layout + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct f32x4_align4(pub [f32; 4]); + +impl CpuLayout for f32x4_align4 { + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.set_align(shame::any::U32PowerOf2::_4); + layout + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct f32x3_align4(pub [f32; 3]); + +// the tests assume that this is the alignment of glam vecs. +static_assertions::assert_eq_align!(glam::Vec2, f32x2_align4); +static_assertions::assert_eq_align!(glam::Vec3, f32x3_align4); +static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); + +impl CpuLayout for f32x3_align4 { + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.set_align(shame::any::U32PowerOf2::_4.into()); + layout + } +} + +#[derive(Clone, Copy)] +#[repr(C, align(16))] +struct f32x3_size32(pub [f32; 3], [u8; 20]); + +impl CpuLayout for f32x3_size32 { + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } +} + + + +#[test] +fn unsized_struct_vec3_align_layout_eq() { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: u32x1, + c: sm::Array, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: [f32x3_cpu], + } + + assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[test] +#[rustfmt::skip] fn top_level_align_ignore() { + #[derive(sm::GpuLayout)] + struct OnGpu { // size=16, align=16 + a: f32x4, // size=16, align=16 + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { // size=16, align=4 + a: f32x4_align4, // size=16, align=4 + } + + // the alignment on the top level of the layout doesn't matter. + // two layouts are only considered different if an alignment mismatch + // leads to different offsets of fields or array elements + assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[test] +#[rustfmt::skip] fn struct_align_round_up() { + #[derive(sm::GpuLayout)] + struct OnGpu { // size=round_up(16, 12)=16, align=16 + a: f32x3, // size=12, align=16 + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { // size=12, align=4 + a: f32x3_align4, + } + assert_ne!(gpu_layout::(), cpu_layout::()); + assert!(gpu_layout::().byte_size() == Some(16)); + assert!(gpu_layout::().align().as_u32() == 16); + assert!(cpu_layout::().byte_size() == Some(12)); + assert!(cpu_layout::().align().as_u32() == 4); +} + +#[test] +fn unsized_struct_nested_vec3_align_layout_eq() { + #[derive(sm::GpuLayout)] + struct InnerGpu { + a: f32x1, + b: u32x1, + } + + #[derive(sm::CpuLayout, Clone)] + #[repr(C)] + struct InnerCpu { + a: f32, + b: u32, + } + + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: u32x1, + c: sm::Array>, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: [InnerCpu], + } + + assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[test] +fn unsized_array_layout_eq() { + assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); + assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); +} + +#[test] +fn layouts_mismatch() { + #[derive(sm::GpuLayout)] + struct OnGpuMore { + a: f32x1, + b: u32x1, + c: i32x1, + d: i32x1, + } + + #[derive(sm::GpuLayout)] + struct OnGpuLess { + a: f32x1, + b: u32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: u32, + c: i32, + } + + assert_ne!(gpu_layout::(), cpu_layout::()); + assert_ne!(gpu_layout::(), cpu_layout::()); +} + +#[test] +fn external_vec_type() { + // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` + pub mod my_mod { + use shame::gpu_layout; + use shame as sm; + use sm::aliases::*; + use sm::GpuLayout as _; + + pub trait CpuLayoutExt { + fn cpu_layout() -> shame::TypeLayout; + } + + impl CpuLayoutExt for glam::Vec4 { + fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + } + + impl CpuLayoutExt for glam::Vec3 { + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.set_align(sm::any::U32PowerOf2::_4); + layout + } + } + } + + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x4, + b: f32x4, + } + + use my_mod::CpuLayoutExt as _; // makes `glam::Vec4::layout()` compile in the derive generated code. + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: glam::Vec4, + b: glam::Vec4, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + + #[derive(sm::GpuLayout)] + struct OnGpu2 { + a: f32x3, + b: f32x3, + #[align(16)] + c: f32x4, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu2 { + a: glam::Vec3, + b: glam::Vec3, + c: glam::Vec4, + } + + assert_ne!(gpu_layout::(), cpu_layout::()); + + // TODO: delete or use compile fail test crate like trybuild to make + // sure that align and size attributes aren't allowed on packed structs. + // #[derive(sm::GpuLayout)] + // #[gpu_repr(packed)] + // struct OnGpu2Packed { + // a: f32x3, + // b: f32x3, + // #[align(16)] + // c: f32x4, + // } + + // assert_eq!(gpu_layout::(), cpu_layout::()); +} + +#[test] +#[rustfmt::skip] fn gpu_repr_packed_test() { + { + #[derive(sm::GpuLayout)] + #[gpu_repr(packed)] + struct OnGpu { + pos: f32x3, + nor: f32x3, + uv : f32x2, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + pos: f32x3_align4, + nor: f32x3_align4, + uv : f32x2_align4, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + } + { + // TODO: delete or use compile fail test crate like trybuild to make + // sure that align and size attributes aren't allowed on packed structs. + // #[derive(sm::GpuLayout)] + // #[gpu_repr(packed)] + // struct OnGpu { + // pos: f32x3, + // nor: f32x3, + // #[align(8)] uv : f32x2, + // } + + // #[derive(sm::CpuLayout)] + // #[repr(C)] + // struct OnCpu { + // pos: f32x3_align4, + // nor: f32x3_align4, + // uv : f32x2_cpu, + // } + + // assert_eq!(gpu_layout::(), cpu_layout::()); + // enum __ where OnGpu: sm::VertexLayout {} + } +} From 1610c20e0c9fe06343af5efd436605d3f59acf1a Mon Sep 17 00:00:00 2001 From: chronicl Date: Wed, 23 Jul 2025 02:45:33 +0200 Subject: [PATCH 139/182] layout mismatch cleanup and some comments --- .../rust_types/type_layout/compatible_with.rs | 78 ++++-- .../rust_types/type_layout/display.rs | 261 +++++++++++++++++ .../src/frontend/rust_types/type_layout/eq.rs | 5 +- .../type_layout/layoutable/align_size.rs | 1 + .../frontend/rust_types/type_layout/mod.rs | 263 +----------------- shame/src/ir/ir_type/layout_constraints.rs | 2 +- 6 files changed, 319 insertions(+), 291 deletions(-) create mode 100644 shame/src/frontend/rust_types/type_layout/display.rs diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index b07770f..d9337da 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -2,14 +2,29 @@ use std::fmt::Write; use crate::{ any::layout::StructLayout, + call_info, common::prettify::{set_color, UnwrapOrStr}, - frontend::rust_types::type_layout::{ArrayLayout, LayoutInfo}, - ir::ir_type::max_u64_po2_dividing, + frontend::rust_types::type_layout::{display::LayoutInfo, ArrayLayout}, + ir::{ir_type::max_u64_po2_dividing, recording::Context}, TypeLayout, }; -use super::{layoutable::LayoutableType, Repr, DEFAULT_REPR}; +use super::{layoutable::LayoutableType, Repr}; +/// `TypeLayoutCompatibleWith` is a `TypeLayoutRecipe` with the additional +/// guarantee that the resulting `TypeLayout` is useable in the specified `AddressSpace`. +/// +/// The address spaces are language specific. For example, in WGSL there are two address spaces: +/// [`WgslStorage`] and [`WgslUniform`]. +/// +/// To be "useable" or "compatible with" an address space means that the type layout +/// - satisfies the layout requirements of the address space +/// - is representable in the target language +/// +/// Wgsl has only one representation of types - there is no choice between std140 and std430 +/// like in glsl - so to be representable in wgsl means that the type layout produced by +/// the recipe is the same as the one produced by the same recipe but with all structs +/// in the recipe using Repr::Storage, which is what shame calls wgsl's representation/layout algorithm. pub struct TypeLayoutCompatibleWith { recipe: LayoutableType, _phantom: std::marker::PhantomData, @@ -27,22 +42,28 @@ impl TypeLayoutCompatibleWith { } // Check for layout errors - let recipe_unified = recipe.to_unified_repr(address_space.matching_repr()); - let layout_unified = recipe_unified.layout(); - if layout != layout_unified { - match try_find_mismatch(&layout, &layout_unified) { - Some(mismatch) => { - return Err(LayoutError { - recipe, - address_space, - mismatch, + match address_space { + AddressSpaceEnum::WgslStorage | AddressSpaceEnum::WgslUniform => { + let recipe_unified = recipe.to_unified_repr(address_space.matching_repr()); + let layout_unified = recipe_unified.layout(); + if layout != layout_unified { + match try_find_mismatch(&layout, &layout_unified) { + Some(mismatch) => { + return Err(AddressSpaceError::DoesntSatisfyRequirements(LayoutError { + recipe, + address_space, + mismatch, + colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(false), + })); + } + None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), } - .into()); } - None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), } } + Ok(Self { recipe, _phantom: std::marker::PhantomData, @@ -89,8 +110,10 @@ impl AddressSpaceEnum { #[derive(thiserror::Error, Debug, Clone)] pub enum AddressSpaceError { - #[error("{0}")] - LayoutError(#[from] LayoutError), + #[error("Address space requirements not satisfied:\n{0}")] + DoesntSatisfyRequirements(LayoutError), + #[error("Not representable in target language:\n{0}")] + NotRepresentable(LayoutError), #[error("Unknown layout error occured for {0} in {1}.")] UnknownLayoutError(LayoutableType, AddressSpaceEnum), #[error( @@ -107,15 +130,12 @@ pub struct LayoutError { recipe: LayoutableType, address_space: AddressSpaceEnum, mismatch: LayoutMismatch, + colored: bool, } impl std::error::Error for LayoutError {} impl std::fmt::Display for LayoutError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use TypeLayout::*; - - let colored = todo!(); - let types_are_the_same = "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same"; match &self.mismatch { LayoutMismatch::TopLevel { @@ -159,11 +179,11 @@ impl std::fmt::Display for LayoutError { array_left.byte_stride )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; - write_struct(f, struct_left, Some(*field_index), colored)?; + write_struct(f, struct_left, Some(*field_index), self.colored)?; } StructMismatch::FieldByteSize { field_index } => { - let field_left = struct_left.fields[*field_index]; - let field_right = struct_right.fields[*field_index]; + let field_left = &struct_left.fields[*field_index]; + let field_right = &struct_right.fields[*field_index]; writeln!( f, @@ -175,11 +195,11 @@ impl std::fmt::Display for LayoutError { UnwrapOrStr(field_left.ty.byte_size(), "") )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; - write_struct(f, struct_left, Some(*field_index), colored)?; + write_struct(f, struct_left, Some(*field_index), self.colored)?; } StructMismatch::FieldOffset { field_index } => { - let field_left = struct_left.fields[*field_index]; - let field_right = struct_right.fields[*field_index]; + let field_left = &struct_left.fields[*field_index]; + let field_right = &struct_right.fields[*field_index]; let field_name = &field_left.name; let offset = field_left.rel_byte_offset; let expected_align = field_right.ty.align().as_u64(); @@ -191,7 +211,7 @@ impl std::fmt::Display for LayoutError { field_name, struct_left.name, expected_align, self.address_space, offset, actual_align )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; - write_struct(f, struct_left, Some(*field_index), colored)?; + write_struct(f, struct_left, Some(*field_index), self.colored)?; writeln!(f, "Potential solutions include:")?; @@ -271,11 +291,11 @@ where #[derive(Debug, Clone)] pub enum LayoutMismatch { - /// `layout1` and `layout2` are always top level layouts. + /// `layout1` and `layout2` are always the top level layouts. /// /// For example, in case of `Array>`, it's always the layout /// of `Array>` even if the array stride mismatch - /// may be happening for the inner `Array`. + /// is happening for the inner `Array`. TopLevel { layout_left: TypeLayout, layout_right: TypeLayout, diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs new file mode 100644 index 0000000..0349adc --- /dev/null +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -0,0 +1,261 @@ +//! This module provides the `Display` implementation for `TypeLayout` and also contains +//! a `StructWriter` which can be used to write the layout of a struct piece by piece +//! with configurable layout information. + +use std::fmt::{Display, Write}; + +use crate::{ + any::{ + layout::{ArrayLayout, StructLayout}, + U32PowerOf2, + }, + common::prettify::UnwrapOrStr, + TypeLayout, +}; + +impl Display for TypeLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, LayoutInfo::ALL) } +} + +impl TypeLayout { + /// a short name for this `TypeLayout`, useful for printing inline + pub fn short_name(&self) -> String { + use TypeLayout::*; + + match &self { + Vector(v) => v.ty.to_string(), + PackedVector(v) => v.ty.to_string(), + Matrix(m) => m.ty.to_string(), + Array(a) => a.short_name(), + Struct(s) => s.short_name(), + } + } + + pub(crate) fn to_string_with_layout_information(&self, layout_info: LayoutInfo) -> Result { + let mut s = String::new(); + self.write(&mut s, layout_info)?; + Ok(s) + } + + pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { + use TypeLayout::*; + + match self { + Vector(_) | PackedVector(_) | Matrix(_) | Array(_) => { + let plain = self.short_name(); + + let stride = match self { + Array(a) => Some(a.byte_stride), + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, + }; + let info_offset = plain.len() + 1; + + // Write header if some layout information is requested + if layout_info != LayoutInfo::NONE { + writeln!(f, "{:info_offset$}{}", "", layout_info.header())?; + } + + // Write the type name and layout information + let info = layout_info.format(None, self.align(), self.byte_size(), stride); + writeln!(f, "{plain:info_offset$}{info}")?; + } + Struct(s) => s.write(f, layout_info)?, + }; + + Ok(()) + } +} + +impl StructLayout { + pub fn short_name(&self) -> String { self.name.to_string() } + + pub(crate) fn to_string_with_layout_info(&self, layout_info: LayoutInfo) -> Result { + let mut s = String::new(); + self.write(&mut s, layout_info)?; + Ok(s) + } + + pub(crate) fn writer(&self, layout_info: LayoutInfo) -> StructWriter<'_> { StructWriter::new(self, layout_info) } + + pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { + use TypeLayout::*; + + let mut writer = self.writer(layout_info); + writer.writeln_header(f)?; + writer.writeln_struct_declaration(f)?; + for i in 0..self.fields.len() { + writer.writeln_field(f, i)?; + } + writer.writeln_struct_end(f) + } +} + +impl ArrayLayout { + pub fn short_name(&self) -> String { + match self.len { + Some(n) => format!("array<{}, {n}>", self.element_ty.short_name()), + None => format!("array<{}, runtime-sized>", self.element_ty.short_name()), + } + } +} + +/// A bitmask that indicates which layout information should be displayed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LayoutInfo(u8); +#[rustfmt::skip] +impl LayoutInfo { + pub const NONE: Self = Self(0); + pub const OFFSET: Self = Self(1 << 0); + pub const ALIGN: Self = Self(1 << 1); + pub const SIZE: Self = Self(1 << 2); + pub const STRIDE: Self = Self(1 << 3); + pub const ALL: Self = Self(Self::OFFSET.0 | Self::ALIGN.0 | Self::SIZE.0 | Self::STRIDE.0); +} +impl std::ops::BitOr for LayoutInfo { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { LayoutInfo(self.0 | rhs.0) } +} +impl LayoutInfo { + pub fn contains(&self, other: Self) -> bool { (self.0 & other.0) == other.0 } + + pub fn header(&self) -> String { + let mut parts = Vec::with_capacity(4); + for (info, info_str) in [ + (Self::OFFSET, "offset"), + (Self::ALIGN, "align"), + (Self::SIZE, "size"), + (Self::STRIDE, "stride"), + ] { + if self.contains(info) { + parts.push(info_str); + } + } + parts.join(" ") + } + + pub fn format(&self, offset: Option, align: U32PowerOf2, size: Option, stride: Option) -> String { + let infos: [(Self, &'static str, &dyn Display); 4] = [ + (Self::OFFSET, "offset", &UnwrapOrStr(offset, "")), + (Self::ALIGN, "align", &align.as_u32()), + (Self::SIZE, "size", &UnwrapOrStr(size, "")), + (Self::STRIDE, "stride", &UnwrapOrStr(stride, "")), + ]; + let mut parts = Vec::with_capacity(4); + for (info, info_str, value) in infos { + if self.contains(info) { + parts.push(format!("{:>info_width$}", value, info_width = info_str.len())); + } + } + parts.join(" ") + } +} + +pub struct StructWriter<'a> { + s: &'a StructLayout, + tab: &'static str, + layout_info: LayoutInfo, + layout_info_offset: usize, +} + +impl<'a> StructWriter<'a> { + pub fn new(s: &'a StructLayout, layout_info: LayoutInfo) -> Self { + let mut this = Self { + s, + // Could make this configurable + tab: " ", + layout_info, + layout_info_offset: 0, + }; + this.set_layout_info_offset_auto(None); + this + } + + pub(crate) fn layout_info_offset(&self) -> usize { self.layout_info_offset } + + /// By setting `max_fields` to `Some(n)`, the writer will adjust Self::layout_info_offset + /// to only take into account the first `n` fields of the struct. + pub(crate) fn set_layout_info_offset_auto(&mut self, max_fields: Option) { + let fields = match max_fields { + Some(n) => n.min(self.s.fields.len()), + None => self.s.fields.len(), + }; + let layout_info_offset = (0..fields) + .map(|i| self.field_declaration(i).len()) + .max() + .unwrap_or(0) + .max(self.struct_declaration().len()); + self.layout_info_offset = layout_info_offset; + } + + pub(crate) fn ensure_layout_info_offset(&mut self, min_layout_info_offset: usize) { + self.layout_info_offset = self.layout_info_offset.max(min_layout_info_offset) + } + + pub(crate) fn tab(&self) -> &'static str { self.tab } + + fn struct_declaration(&self) -> String { format!("struct {} {{", self.s.name) } + + fn field_declaration(&self, field_index: usize) -> String { + match self.s.fields.get(field_index) { + Some(field) => format!("{}{}: {},", self.tab, field.name, field.ty.short_name()), + None => String::new(), + } + } + + pub(crate) fn write_header(&self, f: &mut W) -> std::fmt::Result { + if self.layout_info != LayoutInfo::NONE { + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{}", "", self.layout_info.header())?; + } + Ok(()) + } + + pub(crate) fn write_struct_declaration(&self, f: &mut W) -> std::fmt::Result { + let info = self.layout_info.format(None, *self.s.align, self.s.byte_size, None); + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{info}", self.struct_declaration()) + } + + pub(crate) fn write_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { + use TypeLayout::*; + + let field = &self.s.fields[field_index]; + let info = self.layout_info.format( + Some(field.rel_byte_offset), + field.ty.align(), + field.ty.byte_size(), + match &field.ty { + Array(array) => Some(array.byte_stride), + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, + }, + ); + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{info}", self.field_declaration(field_index)) + } + + pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } + + pub(crate) fn writeln_header(&self, f: &mut W) -> std::fmt::Result { + if self.layout_info != LayoutInfo::NONE { + self.write_header(f)?; + writeln!(f)?; + } + Ok(()) + } + + pub(crate) fn writeln_struct_declaration(&self, f: &mut W) -> std::fmt::Result { + self.write_struct_declaration(f)?; + writeln!(f) + } + + pub(crate) fn writeln_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { + self.write_field(f, field_index)?; + writeln!(f) + } + + pub(crate) fn writeln_struct_end(&self, f: &mut W) -> std::fmt::Result { + self.write_struct_end(f)?; + writeln!(f) + } +} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index c13c906..d28ac69 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,4 +1,5 @@ use crate::frontend::rust_types::type_layout::compatible_with::{StructMismatch, TopLevelMismatch}; +use crate::frontend::rust_types::type_layout::display::LayoutInfo; use super::compatible_with::try_find_mismatch; use super::*; @@ -52,7 +53,6 @@ impl LayoutMismatch { layouts: [(&str, &TypeLayout); 2], colored: bool, ) -> Result { - let tab = " "; let [(a_name, a), (b_name, b)] = layouts; let use_256_color_mode = false; @@ -90,7 +90,6 @@ impl LayoutMismatch { array_left, array_right, } => { - let array_type_layout: TypeLayout = array_left.clone().into(); writeln!( f, "The layouts of `{}` and `{}` do not match.", @@ -299,7 +298,7 @@ mod tests { fn test_layout_mismatch_nested() { // For colored output let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); - let mut drawcall = encoder.new_render_pipeline(sm::Indexing::BufferU16); + let _ = encoder.new_render_pipeline(sm::Indexing::BufferU16); // field name mismatch #[derive(GpuLayout)] diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 256f0d2..9137527 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -36,6 +36,7 @@ impl LayoutableType { } } + // Returns a copy of self, but with all struct reprs changed to `repr`. pub fn to_unified_repr(&self, repr: Repr) -> Self { let mut this = self.clone(); this.change_all_repr(repr); diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index eec4771..bc47dfe 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -1,10 +1,10 @@ #![allow(missing_docs)] +#![warn(unused)] //! Everything related to type layouts. use std::{ fmt::{Debug, Display, Write}, hash::Hash, - marker::PhantomData, rc::Rc, }; @@ -13,21 +13,18 @@ use crate::{ call_info, common::{ ignore_eq::IgnoreInEqOrdHash, - prettify::{set_color, UnwrapOrStr}, + prettify::{set_color}, }, ir::{ self, - ir_type::{round_up, CanonName}, + ir_type::{CanonName}, recording::Context, - Len, }, }; -use layoutable::{ - align_size::{StructLayoutCalculator, PACKED_ALIGN}, - LayoutableType, Matrix, Vector, PackedVector, -}; +use layoutable::{Matrix, Vector, PackedVector}; pub(crate) mod compatible_with; +pub(crate) mod display; pub(crate) mod eq; pub(crate) mod layoutable; @@ -142,203 +139,6 @@ impl std::fmt::Display for Repr { } } -impl ArrayLayout { - pub fn short_name(&self) -> String { - match self.len { - Some(n) => format!("array<{}, {n}>", self.element_ty.short_name()), - None => format!("array<{}, runtime-sized>", self.element_ty.short_name()), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct LayoutInfo(u8); -#[rustfmt::skip] -impl LayoutInfo { - pub const NONE: Self = Self(0); - pub const OFFSET: Self = Self(1 << 0); - pub const ALIGN: Self = Self(1 << 1); - pub const SIZE: Self = Self(1 << 2); - pub const STRIDE: Self = Self(1 << 3); - pub const ALL: Self = Self(Self::OFFSET.0 | Self::ALIGN.0 | Self::SIZE.0 | Self::STRIDE.0); -} -impl std::ops::BitOr for LayoutInfo { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self::Output { LayoutInfo(self.0 | rhs.0) } -} -impl LayoutInfo { - pub fn contains(&self, other: Self) -> bool { (self.0 & other.0) == other.0 } - - pub fn header(&self) -> String { - let mut parts = Vec::with_capacity(4); - for (info, info_str) in [ - (Self::OFFSET, "offset"), - (Self::ALIGN, "align"), - (Self::SIZE, "size"), - (Self::STRIDE, "stride"), - ] { - if self.contains(info) { - parts.push(info_str); - } - } - parts.join(" ") - } - - pub fn format(&self, offset: Option, align: U32PowerOf2, size: Option, stride: Option) -> String { - let infos: [(Self, &'static str, &dyn Display); 4] = [ - (Self::OFFSET, "offset", &UnwrapOrStr(offset, "")), - (Self::ALIGN, "align", &align.as_u32()), - (Self::SIZE, "size", &UnwrapOrStr(size, "")), - (Self::STRIDE, "stride", &UnwrapOrStr(stride, "")), - ]; - let mut parts = Vec::with_capacity(4); - for (info, info_str, value) in infos { - if self.contains(info) { - parts.push(format!("{:>info_width$}", value, info_width = info_str.len())); - } - } - parts.join(" ") - } -} - -pub struct StructWriter<'a> { - s: &'a StructLayout, - tab: &'static str, - layout_info: LayoutInfo, - layout_info_offset: usize, -} - -impl<'a> StructWriter<'a> { - pub fn new(s: &'a StructLayout, layout_info: LayoutInfo) -> Self { - let mut this = Self { - s, - tab: " ", - layout_info, - layout_info_offset: 0, - }; - this.set_layout_info_offset_auto(None); - this - } - - pub(crate) fn layout_info_offset(&self) -> usize { self.layout_info_offset } - - /// By setting `max_fields` to `Some(n)`, the writer will adjust Self::layout_info_offset - /// to only take into account the first `n` fields of the struct. - pub(crate) fn set_layout_info_offset_auto(&mut self, max_fields: Option) { - let fields = match max_fields { - Some(n) => n.min(self.s.fields.len()), - None => self.s.fields.len(), - }; - let layout_info_offset = (0..fields) - .map(|i| self.field_declaration(i).len()) - .max() - .unwrap_or(0) - .max(self.struct_declaration().len()); - self.layout_info_offset = layout_info_offset; - } - - pub(crate) fn ensure_layout_info_offset(&mut self, min_layout_info_offset: usize) { - self.layout_info_offset = self.layout_info_offset.max(min_layout_info_offset) - } - - pub(crate) fn tab(&self) -> &'static str { self.tab } - - fn struct_declaration(&self) -> String { format!("struct {} {{", self.s.name) } - - fn field_declaration(&self, field_index: usize) -> String { - match self.s.fields.get(field_index) { - Some(field) => format!("{}{}: {},", self.tab, field.name, field.ty.short_name()), - None => String::new(), - } - } - - pub(crate) fn write_header(&self, f: &mut W) -> std::fmt::Result { - if self.layout_info != LayoutInfo::NONE { - let info_offset = self.layout_info_offset(); - write!(f, "{:info_offset$}{}", "", self.layout_info.header())?; - } - Ok(()) - } - - pub(crate) fn write_struct_declaration(&self, f: &mut W) -> std::fmt::Result { - let info = self.layout_info.format( - None, // offset is not applicable for structs - *self.s.align, - self.s.byte_size, - None, // stride is not applicable for structs - ); - let info_offset = self.layout_info_offset(); - write!(f, "{:info_offset$}{info}", self.struct_declaration()) - } - - pub(crate) fn write_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { - use TypeLayout::*; - - let field = &self.s.fields[field_index]; - let info = self.layout_info.format( - Some(field.rel_byte_offset), - field.ty.align(), - field.ty.byte_size(), - match &field.ty { - Array(array) => Some(array.byte_stride), - Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, - }, - ); - let info_offset = self.layout_info_offset(); - write!(f, "{:info_offset$}{info}", self.field_declaration(field_index)) - } - - pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } - - pub(crate) fn writeln_header(&self, f: &mut W) -> std::fmt::Result { - if self.layout_info != LayoutInfo::NONE { - self.write_header(f)?; - writeln!(f)?; - } - Ok(()) - } - - pub(crate) fn writeln_struct_declaration(&self, f: &mut W) -> std::fmt::Result { - self.write_struct_declaration(f)?; - writeln!(f) - } - - pub(crate) fn writeln_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { - self.write_field(f, field_index)?; - writeln!(f) - } - - pub(crate) fn writeln_struct_end(&self, f: &mut W) -> std::fmt::Result { - self.write_struct_end(f)?; - writeln!(f) - } -} - -impl StructLayout { - pub fn short_name(&self) -> String { self.name.to_string() } - - pub(crate) fn to_string_with_layout_info(&self, layout_info: LayoutInfo) -> Result { - let mut s = String::new(); - self.write(&mut s, layout_info)?; - Ok(s) - } - - pub(crate) fn writer(&self, layout_info: LayoutInfo) -> StructWriter<'_> { StructWriter::new(self, layout_info) } - - pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { - use TypeLayout::*; - - let mut writer = self.writer(layout_info); - writer.writeln_header(f)?; - writer.writeln_struct_declaration(f)?; - for i in 0..self.fields.len() { - writer.writeln_field(f, i)?; - } - writer.writeln_struct_end(f) - } -} - impl TypeLayout { /// Returns the byte size of the represented type. /// @@ -421,53 +221,6 @@ impl TypeLayout { } } - /// a short name for this `TypeLayout`, useful for printing inline - pub fn short_name(&self) -> String { - use TypeLayout::*; - - match &self { - Vector(v) => v.ty.to_string(), - PackedVector(v) => v.ty.to_string(), - Matrix(m) => m.ty.to_string(), - Array(a) => a.short_name(), - Struct(s) => s.short_name(), - } - } - - pub(crate) fn to_string_with_layout_information(&self, layout_info: LayoutInfo) -> Result { - let mut s = String::new(); - self.write(&mut s, layout_info)?; - Ok(s) - } - - pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { - use TypeLayout::*; - - match self { - Vector(_) | PackedVector(_) | Matrix(_) | Array(_) => { - let plain = self.short_name(); - - let stride = match self { - Array(a) => Some(a.byte_stride), - Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, - }; - let info_offset = plain.len() + 1; - - // Write header if some layout information is requested - if layout_info != LayoutInfo::NONE { - writeln!(f, "{:info_offset$}{}", "", layout_info.header())?; - } - - // Write the type name and layout information - let info = layout_info.format(None, self.align(), self.byte_size(), stride); - writeln!(f, "{plain:info_offset$}{info}")?; - } - Struct(s) => s.write(f, layout_info)?, - }; - - Ok(()) - } - pub(crate) fn first_line_of_display_with_ellipsis(&self) -> String { let string = format!("{}", self); string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) @@ -504,12 +257,6 @@ impl From for TypeLayout { fn from(layout: StructLayout) -> Self { TypeLayout::Struct(Rc::new(layout)) } } - -impl Display for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, LayoutInfo::ALL) } -} - - #[cfg(test)] mod tests { use super::*; diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index 692241d..e5d605a 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -6,7 +6,7 @@ use std::{ use thiserror::Error; -use crate::frontend::rust_types::type_layout::{eq::LayoutMismatch, LayoutInfo, TypeLayout}; +use crate::frontend::rust_types::type_layout::{display::LayoutInfo, eq::LayoutMismatch, TypeLayout}; use crate::{ backend::language::Language, call_info, From f256447cbda2a363ac68b0350d0b0959f9984619 Mon Sep 17 00:00:00 2001 From: chronicl Date: Wed, 23 Jul 2025 16:03:04 +0200 Subject: [PATCH 140/182] improve layout mismatch error, rename Repr::Storage -> Repr::Wgsl --- shame/src/frontend/any/render_io.rs | 2 +- .../rust_types/type_layout/compatible_with.rs | 258 +++++++++++------- .../src/frontend/rust_types/type_layout/eq.rs | 153 +++++++---- .../type_layout/layoutable/align_size.rs | 210 +++++++------- .../frontend/rust_types/type_layout/mod.rs | 50 ++-- shame/src/ir/ir_type/tensor.rs | 2 +- shame_derive/src/derive_layout.rs | 8 +- shame_derive/src/util.rs | 10 +- 8 files changed, 407 insertions(+), 286 deletions(-) diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index c329140..1f555ec 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -251,7 +251,7 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - layoutable::array_stride(layout.align(), size, Repr::Storage) + layoutable::array_stride(layout.align(), size, Repr::Wgsl) }; use TypeLayout::*; diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index d9337da..98da333 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -41,10 +41,41 @@ impl TypeLayoutCompatibleWith { (AddressSpaceEnum::WgslUniform, Some(_)) | (AddressSpaceEnum::WgslStorage, _) => {} } - // Check for layout errors + // Check that the type layout is representable in the target language match address_space { AddressSpaceEnum::WgslStorage | AddressSpaceEnum::WgslUniform => { - let recipe_unified = recipe.to_unified_repr(address_space.matching_repr()); + // Wgsl has only one type representation: Repr::Storage, so the layout produced by the recipe + // is representable in wgsl iff the layout produced by the same recipe but with + // all structs in the recipe using Repr::Storage is the same. + let recipe_unified = recipe.to_unified_repr(Repr::Wgsl); + let layout_unified = recipe_unified.layout(); + if layout != layout_unified { + match try_find_mismatch(&layout, &layout_unified) { + Some(mismatch) => { + return Err(AddressSpaceError::NotRepresentable(LayoutError { + recipe, + address_space, + mismatch, + colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(false), + })); + } + None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), + } + } + } + } + + // Check that the type layout satisfies the requirements of the address space + match address_space { + AddressSpaceEnum::WgslStorage => { + // As long as the recipe is representable in wgsl, it satifies the storage address space requirements. + // We already checked that the recipe is representable in wgsl above. + } + AddressSpaceEnum::WgslUniform => { + // Repr::Uniform is made for exactly this purpose: to check that the type layout + // satisfies the requirements of wgsl's uniform address space. + let recipe_unified = recipe.to_unified_repr(Repr::WgslUniform); let layout_unified = recipe_unified.layout(); if layout != layout_unified { match try_find_mismatch(&layout, &layout_unified) { @@ -71,12 +102,23 @@ impl TypeLayoutCompatibleWith { } } +pub trait AddressSpace { + const ADDRESS_SPACE: AddressSpaceEnum; +} +pub struct WgslStorage; +pub struct WgslUniform; +impl AddressSpace for WgslStorage { + const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslStorage; +} +impl AddressSpace for WgslUniform { + const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslUniform; +} + #[derive(Debug, Clone, Copy)] pub enum AddressSpaceEnum { WgslStorage, WgslUniform, } - impl std::fmt::Display for AddressSpaceEnum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -85,25 +127,11 @@ impl std::fmt::Display for AddressSpaceEnum { } } } - -pub trait AddressSpace { - const ADDRESS_SPACE: AddressSpaceEnum; -} - -pub struct WgslStorage; -pub struct WgslUniform; -impl AddressSpace for WgslStorage { - const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslStorage; -} -impl AddressSpace for WgslUniform { - const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslUniform; -} - impl AddressSpaceEnum { - fn matching_repr(&self) -> Repr { + pub fn language(&self) -> &'static str { match self { - AddressSpaceEnum::WgslStorage => Repr::Storage, - AddressSpaceEnum::WgslUniform => Repr::Uniform, + AddressSpaceEnum::WgslStorage => "wgsl", + AddressSpaceEnum::WgslUniform => "wgsl", } } } @@ -112,7 +140,7 @@ impl AddressSpaceEnum { pub enum AddressSpaceError { #[error("Address space requirements not satisfied:\n{0}")] DoesntSatisfyRequirements(LayoutError), - #[error("Not representable in target language:\n{0}")] + #[error("{} is not representable in {}:\n{0}", .0.recipe, .0.address_space.language())] NotRepresentable(LayoutError), #[error("Unknown layout error occured for {0} in {1}.")] UnknownLayoutError(LayoutableType, AddressSpaceEnum), @@ -139,8 +167,8 @@ impl std::fmt::Display for LayoutError { let types_are_the_same = "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same"; match &self.mismatch { LayoutMismatch::TopLevel { - layout_left: layout1, - layout_right: layout2, + layout_left, + layout_right, mismatch, } => match mismatch { TopLevelMismatch::Type => unreachable!("{}", types_are_the_same), @@ -148,13 +176,37 @@ impl std::fmt::Display for LayoutError { array_left, array_right, } => { + if array_left.short_name() == array_right.short_name() { + writeln!( + f, + "`{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + array_right.byte_stride, + self.address_space, + array_left.byte_stride + )?; + } else { + writeln!( + f, + "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + layout_left.short_name(), + array_right.byte_stride, + self.address_space, + array_left.byte_stride + )?; + } + } + // TODO(chronicl) fix byte size message for when the byte size mismatch is happening + // in a nested array + TopLevelMismatch::ByteSize { .. } => { writeln!( f, - "`{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - array_right.byte_stride, + "`{}` has a byte size of {} in {}, but has a byte size of {}.", + layout_left.short_name(), + UnwrapOrStr(layout_left.byte_size(), ""), self.address_space, - array_left.byte_stride + UnwrapOrStr(layout_right.byte_size(), "") )?; } }, @@ -164,10 +216,13 @@ impl std::fmt::Display for LayoutError { mismatch, } => { match mismatch { - StructMismatch::FieldArrayStride { + StructMismatch::FieldLayout { field_index, - array_left, - array_right, + mismatch: + TopLevelMismatch::ArrayStride { + array_left, + array_right, + }, } => { writeln!( f, @@ -181,10 +236,15 @@ impl std::fmt::Display for LayoutError { writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; write_struct(f, struct_left, Some(*field_index), self.colored)?; } - StructMismatch::FieldByteSize { field_index } => { + StructMismatch::FieldLayout { + field_index, + mismatch: TopLevelMismatch::ByteSize { left, right }, + } => { let field_left = &struct_left.fields[*field_index]; let field_right = &struct_right.fields[*field_index]; + // TODO(chronicl) fix byte size message for when the byte size mismatch is happening + // in a nested array writeln!( f, "Field `{}` in `{}` requires a byte size of {} in {}, but has a byte size of {}", @@ -207,7 +267,7 @@ impl std::fmt::Display for LayoutError { writeln!( f, - "Field `{}` in `{}` needs to be {} byte aligned in {}, but has a byte-offset of {} which is only {} byte aligned", + "Field `{}` in `{}` needs to be {} byte aligned in {}, but has a byte-offset of {}, which is only {} byte aligned", field_name, struct_left.name, expected_align, self.address_space, offset, actual_align )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; @@ -242,7 +302,7 @@ impl std::fmt::Display for LayoutError { } match self.address_space { - (AddressSpaceEnum::WgslUniform | AddressSpaceEnum::WgslStorage) => writeln!( + AddressSpaceEnum::WgslUniform | AddressSpaceEnum::WgslStorage => writeln!( f, "More info about the {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", self.address_space @@ -251,7 +311,10 @@ impl std::fmt::Display for LayoutError { } StructMismatch::FieldCount | StructMismatch::FieldName { .. } | - StructMismatch::FieldType { .. } => { + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::Type, + .. + } => { unreachable!("{}", types_are_the_same) } }; @@ -289,13 +352,36 @@ where Ok(()) } +/// Contains information about the layout mismatch between two `TypeLayout`s. +/// +/// The type layouts are traversed depth-first and the first mismatch encountered +/// is reported at its deepest level. +/// +/// In case of nested structs this means that if the field `a` in +/// ``` +/// struct A { +/// a: struct B { ... } +/// } +/// struct AOther { +/// a: struct BOther { ... } +/// } +/// ``` +/// mismatches, because `B` and `BOther` don't match, then the exact mismatch +/// between `B` and `BOther` is reported and not the field mismatch of `a` in `A` and `AOther`. +/// +/// Nested arrays are reported in two levels: `LayoutMismatch::TopLevel` contains the +/// top level / outer most array layout and a `TopLevelMismatch`, which contains the +/// inner type layout where the mismatch is happening. +/// For example if there is an array stride mismatch of the inner array of `Array>`, +/// then `LayoutMismatch::TopLevel` contains the layout of `Array>` and +/// a `TopLevelMismatch::ArrayStride` with the layout of `Array`. +/// +/// A field of nested arrays in a struct is handled in the same way by +/// `LayoutMismatch::Struct` containing the field index, which let's us access the outer +/// most array, and a `TopLevelMismatch`, which let's us access the inner type layout +/// where the mismatch is happening. #[derive(Debug, Clone)] pub enum LayoutMismatch { - /// `layout1` and `layout2` are always the top level layouts. - /// - /// For example, in case of `Array>`, it's always the layout - /// of `Array>` even if the array stride mismatch - /// is happening for the inner `Array`. TopLevel { layout_left: TypeLayout, layout_right: TypeLayout, @@ -311,38 +397,29 @@ pub enum LayoutMismatch { #[derive(Debug, Clone)] pub enum TopLevelMismatch { Type, - /// This contains the array layout where the mismatch is happening, - /// which is not necessarily the top level array. - /// For example, in case of an array stride mismatch of the inner - /// array in Array>, this is Array's layout. + ByteSize { + left: TypeLayout, + right: TypeLayout, + }, ArrayStride { array_left: ArrayLayout, array_right: ArrayLayout, }, } -/// Returns the first mismatch found in the struct fields. -/// /// Field count is checked last. #[derive(Debug, Clone)] pub enum StructMismatch { FieldName { field_index: usize, }, - FieldType { + FieldLayout { field_index: usize, + mismatch: TopLevelMismatch, }, FieldOffset { field_index: usize, }, - FieldByteSize { - field_index: usize, - }, - FieldArrayStride { - field_index: usize, - array_left: ArrayLayout, - array_right: ArrayLayout, - }, FieldCount, } @@ -382,25 +459,19 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O (Array(a1), Array(a2)) => { // Recursively check element types match try_find_mismatch(&a1.element_ty, &a2.element_ty) { - // In case the mismatch isn't related to a struct field mismatch, - // we propagate the error upwards to this array. For example - // Array> vs Array> comparison returns a - // Array> vs Array> type mismatch and not a - // Array vs Array mismatch - // Note that we don't change the TopLevelMismatch::ArrayStride fields though. + // Update the top level layouts and propagate the LayoutMismatch Some(LayoutMismatch::TopLevel { layout_left: layout1, layout_right: layout2, mismatch, - }) => match mismatch { - TopLevelMismatch::Type | TopLevelMismatch::ArrayStride { .. } => { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch, - }); - } - }, + }) => { + return Some(LayoutMismatch::TopLevel { + layout_left: layout1.clone(), + layout_right: layout2.clone(), + mismatch, + }); + } + // Struct mismatch, so it's not a top-level mismatch anymore m @ Some(LayoutMismatch::Struct { .. }) => return m, None => return None, } @@ -439,6 +510,18 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } } + // Check byte size + if layout1.byte_size() != layout2.byte_size() { + return Some(LayoutMismatch::TopLevel { + layout_left: layout1.clone(), + layout_right: layout2.clone(), + mismatch: TopLevelMismatch::ByteSize { + left: layout1.clone(), + right: layout2.clone(), + }, + }); + } + None } @@ -446,9 +529,8 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O for (field_index, (field1, field2)) in struct1.fields.iter().zip(struct2.fields.iter()).enumerate() { // Order of checks is important here. We check in order // - field name - // - field type and if the field is/contains a struct, recursively check its fields + // - field type, byte size and if the field is/contains a struct, recursively check its fields // - field offset - // - field byte size if field1.name != field2.name { return Some(LayoutMismatch::Struct { struct_left: struct1.clone(), @@ -461,32 +543,11 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O if let Some(inner_mismatch) = try_find_mismatch(&field1.ty, &field2.ty) { match inner_mismatch { // If it's a top-level mismatch, convert it to a field mismatch - LayoutMismatch::TopLevel { - mismatch: TopLevelMismatch::Type, - .. - } => { + LayoutMismatch::TopLevel { mismatch, .. } => { return Some(LayoutMismatch::Struct { struct_left: struct1.clone(), struct_right: struct2.clone(), - mismatch: StructMismatch::FieldType { field_index }, - }); - } - LayoutMismatch::TopLevel { - mismatch: - TopLevelMismatch::ArrayStride { - array_left, - array_right, - }, - .. - } => { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldArrayStride { - field_index, - array_left, - array_right, - }, + mismatch: StructMismatch::FieldLayout { field_index, mismatch }, }); } // Pass through nested struct mismatches @@ -502,15 +563,6 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O mismatch: StructMismatch::FieldOffset { field_index }, }); } - - // Check field byte size - if field1.ty.byte_size() != field2.ty.byte_size() { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldByteSize { field_index }, - }); - } } // Check field count diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index d28ac69..0c48098 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,3 +1,4 @@ +use crate::common::prettify::UnwrapOrStr; use crate::frontend::rust_types::type_layout::compatible_with::{StructMismatch, TopLevelMismatch}; use crate::frontend::rust_types::type_layout::display::LayoutInfo; @@ -78,40 +79,49 @@ impl LayoutMismatch { layout_left, layout_right, mismatch, - } => { - match mismatch { - TopLevelMismatch::Type => writeln!( + } => match mismatch { + TopLevelMismatch::Type => writeln!( + f, + "The layouts of `{}` and `{}` do not match, because their types are semantically different.", + layout_left.short_name(), + layout_right.short_name() + )?, + TopLevelMismatch::ArrayStride { + array_left, + array_right, + } => { + writeln!( f, - "The layouts of `{}` and `{}` do not match, because their types are semantically different.", + "The layouts of `{}` and `{}` do not match.", layout_left.short_name(), layout_right.short_name() - )?, - TopLevelMismatch::ArrayStride { - array_left, - array_right, - } => { - writeln!( - f, - "The layouts of `{}` and `{}` do not match.", - layout_left.short_name(), - layout_right.short_name() - )?; - // Using array_left and array_right here, because those are the layouts - // of the deepest array stride mismatch. For example it can be that - // layout_left = Array> - // array_left = Array - // because the deepest array stride mismatch is happening on the inner Array. - writeln!( - f, - "`{}` has a stride of {}, while `{}` has a stride of {}.", - array_left.short_name(), - array_left.byte_stride, - array_right.short_name(), - array_right.byte_stride - )?; - } + )?; + writeln!( + f, + "`{}` has a stride of {}, while `{}` has a stride of {}.", + array_left.short_name(), + array_left.byte_stride, + array_right.short_name(), + array_right.byte_stride + )?; } - } + TopLevelMismatch::ByteSize { left, right } => { + writeln!( + f, + "The layouts of `{}` and `{}` do not match.", + layout_left.short_name(), + layout_right.short_name() + )?; + writeln!( + f, + "`{}` has a byte size of {}, while `{}` has a byte size of {}.", + left.short_name(), + UnwrapOrStr(left.byte_size(), "runtime-sized"), + right.short_name(), + UnwrapOrStr(right.byte_size(), "runtime-sized") + )?; + } + }, Struct { struct_left, struct_right, @@ -131,30 +141,83 @@ impl LayoutMismatch { "The layouts of `{}` and `{}` do not match, because the", struct_left.name, struct_right.name ); - let (mismatch_field_index, layout_info) = match mismatch { + let (mismatch_field_index, layout_info) = match &mismatch { StructMismatch::FieldName { field_index } => { writeln!(f, "names of field {field_index} are different.")?; (Some(field_index), LayoutInfo::NONE) } - StructMismatch::FieldType { field_index } => { - writeln!(f, "type of {} is different.", field_name(field_index))?; + StructMismatch::FieldLayout { + field_index, + mismatch: TopLevelMismatch::Type, + } => { + writeln!(f, "type of {} is different.", field_name(*field_index))?; (Some(field_index), LayoutInfo::NONE) } - StructMismatch::FieldByteSize { field_index } => { - writeln!(f, "byte size of {} is different.", field_name(field_index))?; - (Some(field_index), LayoutInfo::SIZE) + // TODO(chronicl) fix byte size message for when the byte size mismatch is happening + // in a nested array + StructMismatch::FieldLayout { + field_index, + mismatch: TopLevelMismatch::ByteSize { left, right }, + } => { + match struct_left.fields.get(*field_index) { + // Inner type in (nested) array has mismatching byte size + Some(field) if &field.ty != left => { + writeln!( + f, + "byte size of `{}` is {} in `{}` and {} in `{}`.", + left.short_name(), + UnwrapOrStr(left.byte_size(), "runtime-sized"), + struct_left.name, + UnwrapOrStr(right.byte_size(), "runtime-sized"), + struct_right.name, + )?; + // Not showing byte size info, because it can be misleading since + // the inner type is the one that has mismatching byte size. + (Some(field_index), LayoutInfo::NONE) + } + _ => { + writeln!(f, "byte size of {} is different.", field_name(*field_index))?; + (Some(field_index), LayoutInfo::SIZE) + } + } + } + StructMismatch::FieldLayout { + field_index, + mismatch: + TopLevelMismatch::ArrayStride { + array_left, + array_right, + }, + } => { + match struct_left.fields.get(*field_index) { + // Inner type in (nested) array has mismatching stride + Some(field) if field.ty.short_name() != array_left.short_name() => { + writeln!( + f, + "stride of `{}` is {} in `{}` and {} in `{}`.", + array_left.short_name(), + array_left.byte_stride, + struct_left.name, + array_right.byte_stride, + struct_right.name, + )?; + // Not showing stride info, because it can be misleading since + // the inner type is the one that has mismatching stride. + (Some(field_index), LayoutInfo::NONE) + } + _ => { + writeln!(f, "array stride of {} is different.", field_name(*field_index))?; + (Some(field_index), LayoutInfo::STRIDE) + } + } } StructMismatch::FieldOffset { field_index } => { - writeln!(f, "offset of {} is different.", field_name(field_index))?; + writeln!(f, "offset of {} is different.", field_name(*field_index))?; ( Some(field_index), LayoutInfo::OFFSET | LayoutInfo::ALIGN | LayoutInfo::SIZE, ) } - StructMismatch::FieldArrayStride { field_index, .. } => { - writeln!(f, "array stride of {} is different.", field_name(field_index))?; - (Some(field_index), LayoutInfo::STRIDE) - } StructMismatch::FieldCount => { writeln!(f, "number of fields is different.")?; (None, LayoutInfo::NONE) @@ -163,7 +226,7 @@ impl LayoutMismatch { writeln!(f)?; let fields_without_mismatch = match mismatch_field_index { - Some(index) => struct_left.fields.len().min(struct_right.fields.len()).min(index), + Some(index) => struct_left.fields.len().min(struct_right.fields.len()).min(*index), None => struct_left.fields.len().min(struct_right.fields.len()), }; @@ -208,10 +271,8 @@ impl LayoutMismatch { match mismatch { StructMismatch::FieldName { field_index } | - StructMismatch::FieldType { field_index } | - StructMismatch::FieldByteSize { field_index } | - StructMismatch::FieldOffset { field_index } | - StructMismatch::FieldArrayStride { field_index, .. } => { + StructMismatch::FieldLayout { field_index, .. } | + StructMismatch::FieldOffset { field_index } => { // Write mismatching field enable_color(f, hex_left)?; writer_left.write_field(f, field_index)?; diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs index 9137527..63f66f5 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs @@ -230,14 +230,14 @@ impl Vector { pub const fn byte_size(&self, repr: Repr) -> u64 { match repr { - Repr::Storage | Repr::Uniform | Repr::Packed => self.len.as_u64() * self.scalar.byte_size(), + Repr::Wgsl | Repr::WgslUniform | Repr::Packed => self.len.as_u64() * self.scalar.byte_size(), } } pub const fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { Repr::Packed => PACKED_ALIGN, - Repr::Storage | Repr::Uniform => { + Repr::Wgsl | Repr::WgslUniform => { let po2_len = match self.len { Len::X1 | Len::X2 | Len::X4 => self.len.as_u32(), Len::X3 => 4, @@ -264,7 +264,7 @@ impl ScalarType { pub const fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { Repr::Packed => PACKED_ALIGN, - Repr::Storage | Repr::Uniform => match self { + Repr::Wgsl | Repr::WgslUniform => match self { ScalarType::F16 => U32PowerOf2::_2, ScalarType::F32 | ScalarType::U32 | ScalarType::I32 => U32PowerOf2::_4, ScalarType::F64 => U32PowerOf2::_8, @@ -317,7 +317,7 @@ impl Atomic { pub const fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { Repr::Packed => return PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} + Repr::Wgsl | Repr::WgslUniform => {} } self.scalar.as_scalar_type().align(repr) @@ -352,8 +352,8 @@ pub const fn array_size(array_stride: u64, len: NonZeroU32) -> u64 { array_strid pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage => element_align, - Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), + Repr::Wgsl => element_align, + Repr::WgslUniform => round_up_align(U32PowerOf2::_16, element_align), Repr::Packed => PACKED_ALIGN, } } @@ -361,14 +361,14 @@ pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 /// Returns an array's size=>stride (the distance between consecutive elements) given the alignment and size of its elements. pub const fn array_stride(element_align: U32PowerOf2, element_size: u64, repr: Repr) -> u64 { let element_align = match repr { - Repr::Storage => element_align, + Repr::Wgsl => element_align, // This should already be the case, but doesn't hurt to ensure. Repr::Packed => PACKED_ALIGN, // The uniform address space also requires that: // Array elements are aligned to 16 byte boundaries. // That is, StrideOf(array) = 16 × k’ for some positive integer k'. // - https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Repr::Uniform => round_up_align(U32PowerOf2::_16, element_align), + Repr::WgslUniform => round_up_align(U32PowerOf2::_16, element_align), }; round_up(element_align.as_u64(), element_size) @@ -465,7 +465,7 @@ impl StructLayoutCalculator { // Just in case the user didn't already do this. match self.repr { Repr::Packed => field_align = PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} + Repr::Wgsl | Repr::WgslUniform => {} } let size = Self::calculate_byte_size(field_size, custom_min_size); @@ -477,8 +477,8 @@ impl StructLayoutCalculator { // - If a structure member itself has a structure type S, then the number of // bytes between the start of that member and the start of any following // member must be at least roundUp(16, SizeOf(S)). - (Repr::Uniform, true) => round_up(16, offset + size), - (Repr::Storage | Repr::Packed, _) | (Repr::Uniform, false) => offset + size, + (Repr::WgslUniform, true) => round_up(16, offset + size), + (Repr::Wgsl | Repr::Packed, _) | (Repr::WgslUniform, false) => offset + size, }; self.align = self.align.max(align); @@ -499,7 +499,7 @@ impl StructLayoutCalculator { // Just in case the user didn't already do this. match self.repr { Repr::Packed => field_align = PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} + Repr::Wgsl | Repr::WgslUniform => {} } let align = Self::calculate_align(field_align, custom_min_align, self.repr); @@ -526,7 +526,7 @@ impl StructLayoutCalculator { match (self.repr, field_custom_min_align) { // Packed always returns self.next_offset_min regardless of custom_min_align (Repr::Packed, _) => self.next_offset_min, - (Repr::Storage | Repr::Uniform, _) => round_up(field_align.as_u64(), self.next_offset_min), + (Repr::Wgsl | Repr::WgslUniform, _) => round_up(field_align.as_u64(), self.next_offset_min), } } @@ -546,7 +546,7 @@ impl StructLayoutCalculator { repr: Repr, ) -> U32PowerOf2 { match repr { - Repr::Storage | Repr::Uniform => { + Repr::Wgsl | Repr::WgslUniform => { // const align.max(custom_min_align.unwrap_or(U32PowerOf2::_1)) if let Some(min_align) = custom_min_align { align.max(min_align) @@ -562,8 +562,8 @@ impl StructLayoutCalculator { const fn adjust_struct_alignment_for_repr(align: U32PowerOf2, repr: Repr) -> U32PowerOf2 { match repr { // Packedness is ensured by the `LayoutCalculator`. - Repr::Storage => align, - Repr::Uniform => round_up_align(U32PowerOf2::_16, align), + Repr::Wgsl => align, + Repr::WgslUniform => round_up_align(U32PowerOf2::_16, align), Repr::Packed => PACKED_ALIGN, } } @@ -586,17 +586,17 @@ mod tests { // i32, u32, or f32: AlilgnOf(T) = 4, SizeOf(T) = 4 assert_eq!(ScalarType::I32.byte_size(), 4); - assert_eq!(ScalarType::I32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(ScalarType::I32.align(Repr::Wgsl), U32PowerOf2::_4); assert_eq!(ScalarType::U32.byte_size(), 4); - assert_eq!(ScalarType::U32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(ScalarType::U32.align(Repr::Wgsl), U32PowerOf2::_4); assert_eq!(ScalarType::F32.byte_size(), 4); - assert_eq!(ScalarType::F32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(ScalarType::F32.align(Repr::Wgsl), U32PowerOf2::_4); // f16: AlilgnOf(T) = 2, SizeOf(T) = 2 assert_eq!(ScalarType::F16.byte_size(), 2); - assert_eq!(ScalarType::F16.align(Repr::Storage), U32PowerOf2::_2); + assert_eq!(ScalarType::F16.align(Repr::Wgsl), U32PowerOf2::_2); // not found in spec assert_eq!(ScalarType::F64.byte_size(), 8); - assert_eq!(ScalarType::F64.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(ScalarType::F64.align(Repr::Wgsl), U32PowerOf2::_8); // Test atomics let atomic_u32 = Atomic { @@ -606,9 +606,9 @@ mod tests { scalar: ScalarTypeInteger::I32, }; // atomic: AlignOf(T) = 4, SizeOf(T) = 4 - assert_eq!(atomic_u32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(atomic_u32.align(Repr::Wgsl), U32PowerOf2::_4); assert_eq!(atomic_u32.byte_size(), 4); - assert_eq!(atomic_i32.align(Repr::Storage), U32PowerOf2::_4); + assert_eq!(atomic_i32.align(Repr::Wgsl), U32PowerOf2::_4); assert_eq!(atomic_i32.byte_size(), 4); // Test vectors @@ -619,23 +619,23 @@ mod tests { let vec4_f32 = Vector::new(ScalarType::F32, Len::X4); let vec4_f16 = Vector::new(ScalarType::F16, Len::X4); // vec2, T is i32, u32, or f32: AlignOf(T) = 8, SizeOf(T) = 8 - assert_eq!(vec2_f32.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(vec2_f32.byte_size(Repr::Storage), 8); + assert_eq!(vec2_f32.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(vec2_f32.byte_size(Repr::Wgsl), 8); // vec2: AlignOf(T) = 4, SizeOf(T) = 4 - assert_eq!(vec2_f16.align(Repr::Storage), U32PowerOf2::_4); - assert_eq!(vec2_f16.byte_size(Repr::Storage), 4); + assert_eq!(vec2_f16.align(Repr::Wgsl), U32PowerOf2::_4); + assert_eq!(vec2_f16.byte_size(Repr::Wgsl), 4); // vec3, T is i32, u32, or f32: AlignOf(T) = 16, SizeOf(T) = 12 - assert_eq!(vec3_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(vec3_f32.byte_size(Repr::Storage), 12); + assert_eq!(vec3_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(vec3_f32.byte_size(Repr::Wgsl), 12); // vec3: AlignOf(T) = 8, SizeOf(T) = 6 - assert_eq!(vec3_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(vec3_f16.byte_size(Repr::Storage), 6); + assert_eq!(vec3_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(vec3_f16.byte_size(Repr::Wgsl), 6); // vec4, T is i32, u32, or f32: AlignOf(T) = 16, SizeOf(T) = 16 - assert_eq!(vec4_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(vec4_f32.byte_size(Repr::Storage), 16); + assert_eq!(vec4_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(vec4_f32.byte_size(Repr::Wgsl), 16); // vec4: AlignOf(T) = 8, SizeOf(T) = 8 - assert_eq!(vec4_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(vec4_f16.byte_size(Repr::Storage), 8); + assert_eq!(vec4_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(vec4_f16.byte_size(Repr::Wgsl), 8); // Test matrices let mat2x2_f32 = Matrix { @@ -729,59 +729,59 @@ mod tests { rows: Len2::X4, }; // mat2x2: AlignOf(T) = 8, SizeOf(T) = 16 - assert_eq!(mat2x2_f32.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat2x2_f32.byte_size(Repr::Storage), 16); + assert_eq!(mat2x2_f32.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat2x2_f32.byte_size(Repr::Wgsl), 16); // mat2x2: AlignOf(T) = 4, SizeOf(T) = 8 - assert_eq!(mat2x2_f16.align(Repr::Storage), U32PowerOf2::_4); - assert_eq!(mat2x2_f16.byte_size(Repr::Storage), 8); + assert_eq!(mat2x2_f16.align(Repr::Wgsl), U32PowerOf2::_4); + assert_eq!(mat2x2_f16.byte_size(Repr::Wgsl), 8); // mat3x2: AlignOf(T) = 8, SizeOf(T) = 24 - assert_eq!(mat3x2_f32.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat3x2_f32.byte_size(Repr::Storage), 24); + assert_eq!(mat3x2_f32.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat3x2_f32.byte_size(Repr::Wgsl), 24); // mat3x2: AlignOf(T) = 4, SizeOf(T) = 12 - assert_eq!(mat3x2_f16.align(Repr::Storage), U32PowerOf2::_4); - assert_eq!(mat3x2_f16.byte_size(Repr::Storage), 12); + assert_eq!(mat3x2_f16.align(Repr::Wgsl), U32PowerOf2::_4); + assert_eq!(mat3x2_f16.byte_size(Repr::Wgsl), 12); // mat4x2: AlignOf(T) = 8, SizeOf(T) = 32 - assert_eq!(mat4x2_f32.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat4x2_f32.byte_size(Repr::Storage), 32); + assert_eq!(mat4x2_f32.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat4x2_f32.byte_size(Repr::Wgsl), 32); // mat4x2: AlignOf(T) = 4, SizeOf(T) = 16 - assert_eq!(mat4x2_f16.align(Repr::Storage), U32PowerOf2::_4); - assert_eq!(mat4x2_f16.byte_size(Repr::Storage), 16); + assert_eq!(mat4x2_f16.align(Repr::Wgsl), U32PowerOf2::_4); + assert_eq!(mat4x2_f16.byte_size(Repr::Wgsl), 16); // mat2x3: AlignOf(T) = 16, SizeOf(T) = 32 - assert_eq!(mat2x3_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(mat2x3_f32.byte_size(Repr::Storage), 32); + assert_eq!(mat2x3_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(mat2x3_f32.byte_size(Repr::Wgsl), 32); // mat2x3: AlignOf(T) = 8, SizeOf(T) = 16 - assert_eq!(mat2x3_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat2x3_f16.byte_size(Repr::Storage), 16); + assert_eq!(mat2x3_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat2x3_f16.byte_size(Repr::Wgsl), 16); // mat3x3: AlignOf(T) = 16, SizeOf(T) = 48 - assert_eq!(mat3x3_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(mat3x3_f32.byte_size(Repr::Storage), 48); + assert_eq!(mat3x3_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(mat3x3_f32.byte_size(Repr::Wgsl), 48); // mat3x3: AlignOf(T) = 8, SizeOf(T) = 24 - assert_eq!(mat3x3_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat3x3_f16.byte_size(Repr::Storage), 24); + assert_eq!(mat3x3_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat3x3_f16.byte_size(Repr::Wgsl), 24); // mat4x3: AlignOf(T) = 16, SizeOf(T) = 64 - assert_eq!(mat4x3_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(mat4x3_f32.byte_size(Repr::Storage), 64); + assert_eq!(mat4x3_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(mat4x3_f32.byte_size(Repr::Wgsl), 64); // mat4x3: AlignOf(T) = 8, SizeOf(T) = 32 - assert_eq!(mat4x3_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat4x3_f16.byte_size(Repr::Storage), 32); + assert_eq!(mat4x3_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat4x3_f16.byte_size(Repr::Wgsl), 32); // mat2x4: AlignOf(T) = 16, SizeOf(T) = 32 - assert_eq!(mat2x4_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(mat2x4_f32.byte_size(Repr::Storage), 32); + assert_eq!(mat2x4_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(mat2x4_f32.byte_size(Repr::Wgsl), 32); // mat2x4: AlignOf(T) = 8, SizeOf(T) = 16 - assert_eq!(mat2x4_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat2x4_f16.byte_size(Repr::Storage), 16); + assert_eq!(mat2x4_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat2x4_f16.byte_size(Repr::Wgsl), 16); // mat3x4: AlignOf(T) = 16, SizeOf(T) = 48 - assert_eq!(mat3x4_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(mat3x4_f32.byte_size(Repr::Storage), 48); + assert_eq!(mat3x4_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(mat3x4_f32.byte_size(Repr::Wgsl), 48); // mat3x4: AlignOf(T) = 8, SizeOf(T) = 24 - assert_eq!(mat3x4_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat3x4_f16.byte_size(Repr::Storage), 24); + assert_eq!(mat3x4_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat3x4_f16.byte_size(Repr::Wgsl), 24); // mat4x4: AlignOf(T) = 16, SizeOf(T) = 64 - assert_eq!(mat4x4_f32.align(Repr::Storage), U32PowerOf2::_16); - assert_eq!(mat4x4_f32.byte_size(Repr::Storage), 64); + assert_eq!(mat4x4_f32.align(Repr::Wgsl), U32PowerOf2::_16); + assert_eq!(mat4x4_f32.byte_size(Repr::Wgsl), 64); // mat4x4: AlignOf(T) = 8, SizeOf(T) = 32 - assert_eq!(mat4x4_f16.align(Repr::Storage), U32PowerOf2::_8); - assert_eq!(mat4x4_f16.byte_size(Repr::Storage), 32); + assert_eq!(mat4x4_f16.align(Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(mat4x4_f16.byte_size(Repr::Wgsl), 32); // Testing Repr::Uniform and Repr::Packed // @@ -806,23 +806,23 @@ mod tests { for scalar in scalars { // Looks silly, because byte_size doesn't have a repr argument. assert_eq!(scalar.byte_size(), scalar.byte_size()); - assert_eq!(scalar.align(Repr::Storage), scalar.align(Repr::Uniform)); + assert_eq!(scalar.align(Repr::Wgsl), scalar.align(Repr::WgslUniform)); assert_eq!(scalar.align(Repr::Packed), U32PowerOf2::_1); } for atomic in atomics { // Looks silly, because byte_size doesn't have a repr argument. assert_eq!(atomic.byte_size(), atomic.byte_size()); - assert_eq!(atomic.align(Repr::Storage), atomic.align(Repr::Uniform)); + assert_eq!(atomic.align(Repr::Wgsl), atomic.align(Repr::WgslUniform)); assert_eq!(atomic.align(Repr::Packed), U32PowerOf2::_1); } for vector in vectors { - assert_eq!(vector.byte_size(Repr::Storage), vector.byte_size(Repr::Uniform)); - assert_eq!(vector.align(Repr::Storage), vector.align(Repr::Uniform)); + assert_eq!(vector.byte_size(Repr::Wgsl), vector.byte_size(Repr::WgslUniform)); + assert_eq!(vector.align(Repr::Wgsl), vector.align(Repr::WgslUniform)); assert_eq!(vector.align(Repr::Packed), U32PowerOf2::_1); } for matrix in matrices { - assert_eq!(matrix.byte_size(Repr::Storage), matrix.byte_size(Repr::Uniform)); - assert_eq!(matrix.align(Repr::Storage), matrix.align(Repr::Uniform)); + assert_eq!(matrix.byte_size(Repr::Wgsl), matrix.byte_size(Repr::WgslUniform)); + assert_eq!(matrix.align(Repr::Wgsl), matrix.align(Repr::WgslUniform)); assert_eq!(matrix.align(Repr::Packed), U32PowerOf2::_1); } } @@ -836,14 +836,14 @@ mod tests { }; // vec2 is 8 bytes, aligned to 8 bytes - assert_eq!(array.byte_stride(Repr::Storage), 8); - assert_eq!(array.byte_size(Repr::Storage), 40); // 5 * 8 - assert_eq!(array.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(array.byte_stride(Repr::Wgsl), 8); + assert_eq!(array.byte_size(Repr::Wgsl), 40); // 5 * 8 + assert_eq!(array.align(Repr::Wgsl), U32PowerOf2::_8); // Uniform requires 16-byte alignment for array elements - assert_eq!(array.byte_stride(Repr::Uniform), 16); - assert_eq!(array.byte_size(Repr::Uniform), 80); // 5 * 16 - assert_eq!(array.align(Repr::Uniform), U32PowerOf2::_16); + assert_eq!(array.byte_stride(Repr::WgslUniform), 16); + assert_eq!(array.byte_size(Repr::WgslUniform), 80); // 5 * 16 + assert_eq!(array.align(Repr::WgslUniform), U32PowerOf2::_16); // Packed has 1-byte alignment assert_eq!(array.byte_stride(Repr::Packed), 8); @@ -856,11 +856,11 @@ mod tests { let element = SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)); let array = RuntimeSizedArray { element }; - assert_eq!(array.byte_stride(Repr::Storage), 8); - assert_eq!(array.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(array.byte_stride(Repr::Wgsl), 8); + assert_eq!(array.align(Repr::Wgsl), U32PowerOf2::_8); - assert_eq!(array.byte_stride(Repr::Uniform), 16); - assert_eq!(array.align(Repr::Uniform), U32PowerOf2::_16); + assert_eq!(array.byte_stride(Repr::WgslUniform), 16); + assert_eq!(array.align(Repr::WgslUniform), U32PowerOf2::_16); assert_eq!(array.byte_stride(Repr::Packed), 8); assert_eq!(array.align(Repr::Packed), U32PowerOf2::_1); @@ -877,13 +877,13 @@ mod tests { #[test] fn test_array_align() { let element_align = U32PowerOf2::_8; - assert_eq!(array_align(element_align, Repr::Storage), U32PowerOf2::_8); - assert_eq!(array_align(element_align, Repr::Uniform), U32PowerOf2::_16); + assert_eq!(array_align(element_align, Repr::Wgsl), U32PowerOf2::_8); + assert_eq!(array_align(element_align, Repr::WgslUniform), U32PowerOf2::_16); assert_eq!(array_align(element_align, Repr::Packed), U32PowerOf2::_1); let small_align = U32PowerOf2::_4; - assert_eq!(array_align(small_align, Repr::Storage), U32PowerOf2::_4); - assert_eq!(array_align(small_align, Repr::Uniform), U32PowerOf2::_16); + assert_eq!(array_align(small_align, Repr::Wgsl), U32PowerOf2::_4); + assert_eq!(array_align(small_align, Repr::WgslUniform), U32PowerOf2::_16); assert_eq!(array_align(small_align, Repr::Packed), U32PowerOf2::_1); } @@ -893,16 +893,16 @@ mod tests { let element_size = 12; // Storage: round up to element alignment - assert_eq!(array_stride(element_align, element_size, Repr::Storage), 16); + assert_eq!(array_stride(element_align, element_size, Repr::Wgsl), 16); // Uniform: round up to 16-byte alignment - assert_eq!(array_stride(element_align, element_size, Repr::Uniform), 16); + assert_eq!(array_stride(element_align, element_size, Repr::WgslUniform), 16); // Packed: round up to 1-byte alignment (no padding) assert_eq!(array_stride(element_align, element_size, Repr::Packed), 12); } #[test] fn test_layout_calculator_basic() { - let mut calc = StructLayoutCalculator::new(Repr::Storage); + let mut calc = StructLayoutCalculator::new(Repr::Wgsl); // Add a u32 field let offset1 = calc.extend(4, U32PowerOf2::_4, None, None, false); @@ -942,7 +942,7 @@ mod tests { #[test] fn test_layout_calculator_uniform_struct_padding() { - let mut calc = StructLayoutCalculator::new(Repr::Uniform); + let mut calc = StructLayoutCalculator::new(Repr::WgslUniform); // Add a nested struct with size 12 let offset1 = calc.extend(12, U32PowerOf2::_4, None, None, true); @@ -957,7 +957,7 @@ mod tests { #[test] fn test_layout_calculator_custom_sizes_and_aligns() { - let mut calc = StructLayoutCalculator::new(Repr::Storage); + let mut calc = StructLayoutCalculator::new(Repr::Wgsl); // Add field with custom minimum size let offset1 = calc.extend(4, U32PowerOf2::_4, Some(33), None, false); @@ -974,7 +974,7 @@ mod tests { #[test] fn test_layout_calculator_extend_unsized() { - let mut calc = StructLayoutCalculator::new(Repr::Storage); + let mut calc = StructLayoutCalculator::new(Repr::Wgsl); // Add some sized fields first calc.extend(4, U32PowerOf2::_4, None, None, false); @@ -993,15 +993,15 @@ mod tests { SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), ); // Vector is 8 bytes, but field has custom min size of 16 - assert_eq!(field.byte_size(Repr::Storage), 16); - assert_eq!(field.align(Repr::Storage), U32PowerOf2::_8); + assert_eq!(field.byte_size(Repr::Wgsl), 16); + assert_eq!(field.align(Repr::Wgsl), U32PowerOf2::_8); // Test custom alignment let field2 = SizedField::new( FieldOptions::new("test_field2", Some(U32PowerOf2::_16), None), SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), ); - assert_eq!(field2.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(field2.align(Repr::Wgsl), U32PowerOf2::_16); } #[test] @@ -1013,7 +1013,7 @@ mod tests { ); // Array has 8-byte alignment, but field has custom min align of 16 - assert_eq!(field.align(Repr::Storage), U32PowerOf2::_16); + assert_eq!(field.align(Repr::Wgsl), U32PowerOf2::_16); // Custom min align is ignored by packed assert_eq!(field.align(Repr::Packed), U32PowerOf2::_1); } @@ -1025,7 +1025,7 @@ mod tests { "TestStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X1)), // 4 bytes, 4-byte aligned - Repr::Storage, + Repr::Wgsl, ) .extend( "field2", @@ -1060,7 +1060,7 @@ mod tests { "TestStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 8 bytes, 8-byte aligned - Repr::Uniform, + Repr::WgslUniform, ); let (size, align) = sized_struct.byte_size_and_align(); @@ -1076,7 +1076,7 @@ mod tests { "UnsizedStruct", "field1", SizedType::Vector(Vector::new(ScalarType::F32, Len::X2)), // 4 bytes, 4-byte aligned - Repr::Storage, + Repr::Wgsl, ) .extend( "field2", @@ -1102,7 +1102,7 @@ mod tests { assert_eq!(unsized_struct.align(), U32PowerOf2::_8); // Test with different repr - unsized_struct.change_all_repr(Repr::Uniform); + unsized_struct.change_all_repr(Repr::WgslUniform); let mut field_offsets_uniform = unsized_struct.field_offsets(); let (last_offset_uniform, struct_align_uniform) = field_offsets_uniform.last_field_offset_and_struct_align(); assert_eq!(last_offset_uniform, 16); // Different offset in uniform, because array's alignment is 16 diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index bc47dfe..d9a4748 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -1,5 +1,4 @@ #![allow(missing_docs)] -#![warn(unused)] //! Everything related to type layouts. use std::{ @@ -28,7 +27,7 @@ pub(crate) mod display; pub(crate) mod eq; pub(crate) mod layoutable; -pub const DEFAULT_REPR: Repr = Repr::Storage; +pub const DEFAULT_REPR: Repr = Repr::Wgsl; /// The memory layout of a type. /// @@ -116,24 +115,33 @@ pub struct FieldLayout { pub ty: TypeLayout, } -/// Enum of layout rules. +/// Enum of layout algorithms. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Repr { - /// Wgsl storage address space layout - /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Storage, - /// Wgsl uniform address space layout + /// WGSL's layout algorithm + /// https://www.w3.org/TR/WGSL/#alignment-and-size + Wgsl, + /// Modified layout algorithm based on [`Repr::Wgsl`], but with different type + /// alignments and array strides that make the resulting Layout match wgsl's + /// uniform address space requirements. + /// /// https://www.w3.org/TR/WGSL/#address-space-layout-constraints - Uniform, - /// Packed layout. Vertex buffer only. + /// + /// (matrix strides remain unchanged however, which makes this different from the std140 layout for mat2x2) + /// + /// Internally used for checking whether a type can be used in the wgsl's + /// uniform address space + WgslUniform, + /// byte-alignment of everything is 1. Custom alignment attributes + /// in [`TypeLayoutRecipe`] are unsupported. Packed, } impl std::fmt::Display for Repr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Repr::Storage => write!(f, "storage"), - Repr::Uniform => write!(f, "uniform"), + Repr::Wgsl => write!(f, "wgsl"), + Repr::WgslUniform => write!(f, "wgsl uniform"), Repr::Packed => write!(f, "packed"), } } @@ -279,8 +287,8 @@ mod tests { // To change the top level arrays repr, we need to set the default repr, // because non-structs inherit repr. - let storage = array.layout_with_default_repr(Repr::Storage); - let uniform = array.layout_with_default_repr(Repr::Uniform); + let storage = array.layout_with_default_repr(Repr::Wgsl); + let uniform = array.layout_with_default_repr(Repr::WgslUniform); let packed = array.layout_with_default_repr(Repr::Packed); assert_eq!(storage.align(), U32PowerOf2::_4); @@ -309,8 +317,8 @@ mod tests { let s = |repr| -> LayoutableType { SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1), repr).into() }; - let storage = s(Repr::Storage).layout(); - let uniform = s(Repr::Uniform).layout(); + let storage = s(Repr::Wgsl).layout(); + let uniform = s(Repr::WgslUniform).layout(); let packed = s(Repr::Packed).layout(); assert_eq!(storage.align(), U32PowerOf2::_4); @@ -331,8 +339,8 @@ mod tests { .into() }; - let storage = s(Repr::Storage).layout(); - let uniform = s(Repr::Uniform).layout(); + let storage = s(Repr::Wgsl).layout(); + let uniform = s(Repr::WgslUniform).layout(); let packed = s(Repr::Packed).layout(); assert_eq!(storage.align(), U32PowerOf2::_4); @@ -368,8 +376,8 @@ mod tests { .into() }; - let storage = s(Repr::Storage).layout(); - let uniform = s(Repr::Uniform).layout(); + let storage = s(Repr::Wgsl).layout(); + let uniform = s(Repr::WgslUniform).layout(); let packed = s(Repr::Packed).layout(); assert_eq!(storage.align(), U32PowerOf2::_4); @@ -395,7 +403,7 @@ mod tests { fn test_unsized_struct_layout() { let mut unsized_struct = UnsizedStruct { name: CanonName::from("TestStruct"), - repr: Repr::Storage, + repr: Repr::Wgsl, sized_fields: vec![ SizedField { name: CanonName::from("field1"), @@ -446,7 +454,7 @@ mod tests { } // Testing uniform representation - unsized_struct.repr = Repr::Uniform; + unsized_struct.repr = Repr::WgslUniform; let recipe: LayoutableType = unsized_struct.into(); println!("{:#?}", recipe); let layout = recipe.layout(); diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 254a98b..85683e6 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -533,7 +533,7 @@ impl PackedVector { pub fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { Repr::Packed => return PACKED_ALIGN, - Repr::Storage | Repr::Uniform => {} + Repr::Wgsl | Repr::WgslUniform => {} } let align = match self.byte_size() { diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 8155208..c6dd188 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -88,10 +88,10 @@ pub fn impl_for_struct( bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") } // if no `#[gpu_repr(_)]` attribute was explicitly specified, we default to `Repr::Storage` - let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Storage); + let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Wgsl); let gpu_repr_shame = match gpu_repr { Repr::Packed => quote!( #re::Repr::Packed ), - Repr::Storage => quote!( #re::Repr::Storage ), + Repr::Wgsl => quote!( #re::Repr::Wgsl ), }; // #[repr(...)] @@ -173,7 +173,7 @@ pub fn impl_for_struct( // ); // } } - Repr::Storage => {} + Repr::Wgsl => {} } if let Some((span, align_lit)) = &fwa.align { @@ -363,7 +363,7 @@ pub fn impl_for_struct( #impl_from_anys }) } - Repr::Storage => { + Repr::Wgsl => { // non gpu_repr(packed) let struct_ref_doc = format!( r#"This struct was generated by `#[derive(shame::GpuLayout)]` diff --git a/shame_derive/src/util.rs b/shame_derive/src/util.rs index 082a492..eae1c82 100644 --- a/shame_derive/src/util.rs +++ b/shame_derive/src/util.rs @@ -29,23 +29,23 @@ pub fn find_literal_list_attr( pub enum Repr { Packed, - Storage, + Wgsl, } pub fn try_find_gpu_repr(attribs: &[syn::Attribute]) -> Result> { - let mut repr = Repr::Storage; + let mut repr = Repr::Wgsl; for a in attribs { if a.path().is_ident("gpu_repr") { a.parse_nested_meta(|meta| { if meta.path.is_ident("packed") { repr = Repr::Packed; return Ok(()); - } else if meta.path.is_ident("storage") { - repr = Repr::Storage; + } else if meta.path.is_ident("wgsl") { + repr = Repr::Wgsl; return Ok(()); } - Err(meta.error("unrecognized `gpu_repr`. Did you mean `gpu_repr(packed)`?")) + Err(meta.error("unrecognized `gpu_repr`. Did you mean `gpu_repr(packed)` or `gpu_repr(wgsl)`?")) })?; return Ok(Some((a.span(), repr))); From 68f81a83b8faa770deb4aee451ca0807323a6249 Mon Sep 17 00:00:00 2001 From: chronicl Date: Wed, 23 Jul 2025 16:20:20 +0200 Subject: [PATCH 141/182] rename LayoutableType -> TypeLayoutRecipe, layoutable -> recipe --- shame/src/common/proc_macro_reexports.rs | 12 ++-- shame/src/frontend/any/render_io.rs | 6 +- shame/src/frontend/rust_types/array.rs | 8 +-- shame/src/frontend/rust_types/atomic.rs | 12 ++-- .../src/frontend/rust_types/layout_traits.rs | 17 +++--- shame/src/frontend/rust_types/mat.rs | 10 +--- shame/src/frontend/rust_types/packed_vec.rs | 8 +-- shame/src/frontend/rust_types/struct_.rs | 4 +- .../rust_types/type_layout/compatible_with.rs | 22 ++++---- .../frontend/rust_types/type_layout/mod.rs | 27 +++++---- .../{layoutable => recipe}/align_size.rs | 28 +++++----- .../{layoutable => recipe}/builder.rs | 30 +++++----- .../{layoutable => recipe}/ir_compat.rs | 40 ++++++------- .../type_layout/{layoutable => recipe}/mod.rs | 43 ++++++-------- .../{layoutable => recipe}/to_layout.rs | 10 ++-- shame/src/frontend/rust_types/vec.rs | 2 +- shame/src/ir/ir_type/tensor.rs | 2 +- shame/src/ir/pipeline/wip_pipeline.rs | 4 +- shame/src/lib.rs | 56 +++++++++---------- shame_derive/src/derive_layout.rs | 8 +-- 20 files changed, 166 insertions(+), 183 deletions(-) rename shame/src/frontend/rust_types/type_layout/{layoutable => recipe}/align_size.rs (97%) rename shame/src/frontend/rust_types/type_layout/{layoutable => recipe}/builder.rs (84%) rename shame/src/frontend/rust_types/type_layout/{layoutable => recipe}/ir_compat.rs (87%) rename shame/src/frontend/rust_types/type_layout/{layoutable => recipe}/mod.rs (80%) rename shame/src/frontend/rust_types/type_layout/{layoutable => recipe}/to_layout.rs (90%) diff --git a/shame/src/common/proc_macro_reexports.rs b/shame/src/common/proc_macro_reexports.rs index 411605e..56e2789 100644 --- a/shame/src/common/proc_macro_reexports.rs +++ b/shame/src/common/proc_macro_reexports.rs @@ -24,7 +24,7 @@ pub use crate::frontend::rust_types::reference::Ref; pub use crate::frontend::rust_types::struct_::BufferFields; pub use crate::frontend::rust_types::struct_::SizedFields; pub use crate::frontend::rust_types::type_layout::FieldLayout; -pub use crate::frontend::rust_types::type_layout::layoutable::FieldOptions; +pub use crate::frontend::rust_types::type_layout::recipe::FieldOptions; pub use crate::frontend::rust_types::type_layout::StructLayout; pub use crate::frontend::rust_types::type_layout::Repr; pub use crate::frontend::rust_types::type_layout::TypeLayout; @@ -38,11 +38,11 @@ pub use crate::frontend::rust_types::type_traits::NoBools; pub use crate::frontend::rust_types::type_traits::NoHandles; pub use crate::frontend::rust_types::type_traits::VertexAttribute; pub use crate::frontend::rust_types::type_traits::GpuLayoutField; -pub use crate::frontend::rust_types::type_layout::layoutable::SizedStruct; -pub use crate::frontend::rust_types::type_layout::layoutable::LayoutableType; -pub use crate::frontend::rust_types::type_layout::layoutable::SizedType; -pub use crate::frontend::rust_types::type_layout::layoutable::SizedOrArray; -pub use crate::frontend::rust_types::type_layout::layoutable::builder::StructFromPartsError; +pub use crate::frontend::rust_types::type_layout::recipe::SizedStruct; +pub use crate::frontend::rust_types::type_layout::recipe::TypeLayoutRecipe; +pub use crate::frontend::rust_types::type_layout::recipe::SizedType; +pub use crate::frontend::rust_types::type_layout::recipe::SizedOrArray; +pub use crate::frontend::rust_types::type_layout::recipe::builder::StructFromPartsError; pub use crate::frontend::rust_types::AsAny; pub use crate::frontend::rust_types::GpuType; #[allow(missing_docs)] diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 1f555ec..3e64fb0 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -4,7 +4,7 @@ use thiserror::Error; use crate::any::layout::{Repr}; use crate::frontend::any::Any; -use crate::frontend::rust_types::type_layout::{layoutable, TypeLayout}; +use crate::frontend::rust_types::type_layout::{recipe, TypeLayout}; use crate::{ call_info, common::iterator_ext::try_collect, @@ -108,7 +108,7 @@ impl Any { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VertexAttribFormat { /// regular [`crate::vec`] types - Fine(Len, layoutable::ScalarType), + Fine(Len, recipe::ScalarType), /// packed [`crate::packed::PackedVec`] types Coarse(PackedVector), } @@ -251,7 +251,7 @@ impl Attrib { ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - layoutable::array_stride(layout.align(), size, Repr::Wgsl) + recipe::array_stride(layout.align(), size, Repr::Wgsl) }; use TypeLayout::*; diff --git a/shame/src/frontend/rust_types/array.rs b/shame/src/frontend/rust_types/array.rs index 4ee1156..7c66084 100644 --- a/shame/src/frontend/rust_types/array.rs +++ b/shame/src/frontend/rust_types/array.rs @@ -5,7 +5,7 @@ use super::len::x1; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable, AccessModeWritable, Read}; use super::scalar_type::ScalarTypeInteger; -use super::type_layout::{self, layoutable, TypeLayout, ArrayLayout}; +use super::type_layout::{self, recipe, TypeLayout, ArrayLayout}; use super::type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, }; @@ -165,10 +165,10 @@ impl ToGpuType for Array { } impl GpuLayout for Array { - fn layout_recipe() -> layoutable::LayoutableType { + fn layout_recipe() -> recipe::TypeLayoutRecipe { match N::LEN { - Some(n) => layoutable::SizedArray::new(Rc::new(T::layout_recipe_sized()), n).into(), - None => layoutable::RuntimeSizedArray::new(T::layout_recipe_sized()).into(), + Some(n) => recipe::SizedArray::new(Rc::new(T::layout_recipe_sized()), n).into(), + None => recipe::RuntimeSizedArray::new(T::layout_recipe_sized()).into(), } } diff --git a/shame/src/frontend/rust_types/atomic.rs b/shame/src/frontend/rust_types/atomic.rs index 3b6e8f8..f6a465c 100644 --- a/shame/src/frontend/rust_types/atomic.rs +++ b/shame/src/frontend/rust_types/atomic.rs @@ -6,11 +6,7 @@ use super::{ mem::{AddressSpace, AddressSpaceAtomic}, reference::{AccessMode, AccessModeReadable, ReadWrite}, scalar_type::{ScalarType, ScalarTypeInteger}, - type_layout::{ - self, - layoutable::{self}, - TypeLayout, - }, + type_layout::{self, TypeLayout}, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -18,7 +14,7 @@ use super::{ vec::vec, AsAny, GpuType, To, ToGpuType, }; -use crate::{frontend::rust_types::reference::Ref}; +use crate::frontend::rust_types::{reference::Ref, type_layout::recipe}; use crate::{ boolx1, frontend::{ @@ -134,8 +130,8 @@ impl GetAllFields for Atomic { } impl GpuLayout for Atomic { - fn layout_recipe() -> layoutable::LayoutableType { - layoutable::Atomic { + fn layout_recipe() -> recipe::TypeLayoutRecipe { + recipe::Atomic { scalar: T::SCALAR_TYPE_INTEGER, } .into() diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 23a5429..24332d0 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -1,4 +1,4 @@ -use crate::any::layout::{LayoutableType, Repr, SizedType}; +use crate::any::layout::{TypeLayoutRecipe, Repr, SizedType}; use crate::call_info; use crate::common::po2::U32PowerOf2; use crate::common::proc_macro_utils::{self, repr_c_struct_layout, ReprCError, ReprCField}; @@ -10,7 +10,6 @@ use crate::frontend::encoding::buffer::{BufferAddressSpace, BufferInner, BufferR use crate::frontend::encoding::{EncodingError, EncodingErrorKind}; use crate::frontend::error::InternalError; use crate::frontend::rust_types::len::*; -use crate::frontend::rust_types::type_layout::layoutable::ScalarType; use crate::frontend::rust_types::type_layout::{ArrayLayout, VectorLayout}; use crate::ir::ir_type::{ align_of_array, align_of_array_from_element_alignment, byte_size_of_array_from_stride_len, round_up, @@ -24,7 +23,7 @@ use super::error::FrontendError; use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; -use super::type_layout::layoutable::{self, array_stride, Vector}; +use super::type_layout::recipe::{self, array_stride, Vector, ScalarType}; use super::type_layout::{self, FieldLayout, StructLayout, TypeLayout, DEFAULT_REPR}; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, @@ -138,16 +137,16 @@ use std::rc::Rc; /// pub trait GpuLayout { /// TODO(chronicl) - fn layout_recipe() -> LayoutableType; + fn layout_recipe() -> TypeLayoutRecipe; /// TODO(chronicl) fn layout_recipe_sized() -> SizedType where Self: GpuSized, { match Self::layout_recipe() { - LayoutableType::Sized(s) => s, - LayoutableType::RuntimeSizedArray(_) | LayoutableType::UnsizedStruct(_) => { - unreachable!("Self is GpuSized, which these LayoutableType variants aren't.") + TypeLayoutRecipe::Sized(s) => s, + TypeLayoutRecipe::RuntimeSizedArray(_) | TypeLayoutRecipe::UnsizedStruct(_) => { + unreachable!("Self is GpuSized, which these TypeLayoutRecipe variants aren't.") } } } @@ -559,7 +558,7 @@ where } impl GpuLayout for GpuT { - fn layout_recipe() -> layoutable::LayoutableType { todo!() } + fn layout_recipe() -> recipe::TypeLayoutRecipe { todo!() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { Some(Ok(( @@ -720,7 +719,7 @@ fn cpu_layout_of_scalar(scalar: ScalarType) -> TypeLayout { align: U32PowerOf2::try_from(align as u32) .expect("aligns are power of 2s in rust") .into(), - ty: Vector::new(scalar, layoutable::Len::X1), + ty: Vector::new(scalar, recipe::Len::X1), debug_is_atomic: false, } .into() diff --git a/shame/src/frontend/rust_types/mat.rs b/shame/src/frontend/rust_types/mat.rs index bd695fe..8ccc8d0 100644 --- a/shame/src/frontend/rust_types/mat.rs +++ b/shame/src/frontend/rust_types/mat.rs @@ -7,11 +7,7 @@ use super::{ mem::AddressSpace, reference::{AccessMode, AccessModeReadable}, scalar_type::{ScalarType, ScalarTypeFp}, - type_layout::{ - self, - layoutable::{self}, - TypeLayout, - }, + type_layout::{self, recipe, TypeLayout}, type_traits::{ BindingArgs, EmptyRefFields, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, @@ -55,8 +51,8 @@ impl Default for mat { } impl GpuLayout for mat { - fn layout_recipe() -> layoutable::LayoutableType { - layoutable::Matrix { + fn layout_recipe() -> recipe::TypeLayoutRecipe { + recipe::Matrix { columns: C::LEN2, rows: R::LEN2, scalar: T::SCALAR_TYPE_FP, diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 0d788a8..537419d 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -22,7 +22,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{self, layoutable, Repr, TypeLayout, DEFAULT_REPR}, + type_layout::{self, recipe, Repr, TypeLayout, DEFAULT_REPR}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -132,8 +132,8 @@ impl NoHandles for PackedVec {} impl NoAtomics for PackedVec {} impl GpuLayout for PackedVec { - fn layout_recipe() -> layoutable::LayoutableType { - layoutable::PackedVector { + fn layout_recipe() -> recipe::TypeLayoutRecipe { + recipe::PackedVector { scalar_type: T::SCALAR_TYPE, bits_per_component: T::BITS_PER_COMPONENT, len: L::LEN_EVEN, @@ -142,7 +142,7 @@ impl GpuLayout for PackedVec { } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { - let sized_ty: layoutable::SizedType = Self::layout_recipe_sized(); + let sized_ty: recipe::SizedType = Self::layout_recipe_sized(); let name = sized_ty.to_string().into(); let layout = sized_ty.layout(DEFAULT_REPR); Some(Ok((name, layout))) diff --git a/shame/src/frontend/rust_types/struct_.rs b/shame/src/frontend/rust_types/struct_.rs index 04bd4bb..ed9f9ff 100644 --- a/shame/src/frontend/rust_types/struct_.rs +++ b/shame/src/frontend/rust_types/struct_.rs @@ -22,7 +22,7 @@ use std::{ }; use super::layout_traits::{GetAllFields, GpuLayout}; -use super::type_layout::{self, layoutable, TypeLayout}; +use super::type_layout::{self, recipe, TypeLayout}; use super::type_traits::{GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoBools}; use super::{ error::FrontendError, @@ -134,7 +134,7 @@ impl Deref for Struct { } impl GpuLayout for Struct { - fn layout_recipe() -> layoutable::LayoutableType { T::layout_recipe() } + fn layout_recipe() -> recipe::TypeLayoutRecipe { T::layout_recipe() } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { T::cpu_type_name_and_layout().map(|x| x.map(|(name, l)| (format!("Struct<{name}>").into(), l))) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 98da333..7febede 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -9,7 +9,7 @@ use crate::{ TypeLayout, }; -use super::{layoutable::LayoutableType, Repr}; +use super::{recipe::TypeLayoutRecipe, Repr}; /// `TypeLayoutCompatibleWith` is a `TypeLayoutRecipe` with the additional /// guarantee that the resulting `TypeLayout` is useable in the specified `AddressSpace`. @@ -24,14 +24,14 @@ use super::{layoutable::LayoutableType, Repr}; /// Wgsl has only one representation of types - there is no choice between std140 and std430 /// like in glsl - so to be representable in wgsl means that the type layout produced by /// the recipe is the same as the one produced by the same recipe but with all structs -/// in the recipe using Repr::Storage, which is what shame calls wgsl's representation/layout algorithm. +/// in the recipe using Repr::Wgsl, which is what shame calls wgsl's representation/layout algorithm. pub struct TypeLayoutCompatibleWith { - recipe: LayoutableType, + recipe: TypeLayoutRecipe, _phantom: std::marker::PhantomData, } impl TypeLayoutCompatibleWith { - pub fn try_from(recipe: LayoutableType) -> Result { + pub fn try_from(recipe: TypeLayoutRecipe) -> Result { let address_space = AS::ADDRESS_SPACE; let layout = recipe.layout(); @@ -44,9 +44,9 @@ impl TypeLayoutCompatibleWith { // Check that the type layout is representable in the target language match address_space { AddressSpaceEnum::WgslStorage | AddressSpaceEnum::WgslUniform => { - // Wgsl has only one type representation: Repr::Storage, so the layout produced by the recipe + // Wgsl has only one type representation: Repr::Wgsl, so the layout produced by the recipe // is representable in wgsl iff the layout produced by the same recipe but with - // all structs in the recipe using Repr::Storage is the same. + // all structs in the recipe using Repr::Wgsl is the same. let recipe_unified = recipe.to_unified_repr(Repr::Wgsl); let layout_unified = recipe_unified.layout(); if layout != layout_unified { @@ -73,7 +73,7 @@ impl TypeLayoutCompatibleWith { // We already checked that the recipe is representable in wgsl above. } AddressSpaceEnum::WgslUniform => { - // Repr::Uniform is made for exactly this purpose: to check that the type layout + // Repr::WgslUniform is made for exactly this purpose: to check that the type layout // satisfies the requirements of wgsl's uniform address space. let recipe_unified = recipe.to_unified_repr(Repr::WgslUniform); let layout_unified = recipe_unified.layout(); @@ -143,19 +143,19 @@ pub enum AddressSpaceError { #[error("{} is not representable in {}:\n{0}", .0.recipe, .0.address_space.language())] NotRepresentable(LayoutError), #[error("Unknown layout error occured for {0} in {1}.")] - UnknownLayoutError(LayoutableType, AddressSpaceEnum), + UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), #[error( "The size of `{0}` on the gpu is not known at compile time. {1} \ requires that the size of {0} on the gpu is known at compile time." )] - MustBeSized(LayoutableType, AddressSpaceEnum), + MustBeSized(TypeLayoutRecipe, AddressSpaceEnum), #[error("{0} contains a `PackedVector`, which are not allowed in {1}.")] - MayNotContainPackedVec(LayoutableType, AddressSpaceEnum), + MayNotContainPackedVec(TypeLayoutRecipe, AddressSpaceEnum), } #[derive(Debug, Clone)] pub struct LayoutError { - recipe: LayoutableType, + recipe: TypeLayoutRecipe, address_space: AddressSpaceEnum, mismatch: LayoutMismatch, colored: bool, diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index d9a4748..7a7f8f4 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -20,12 +20,12 @@ use crate::{ recording::Context, }, }; -use layoutable::{Matrix, Vector, PackedVector}; +use recipe::{Matrix, Vector, PackedVector}; pub(crate) mod compatible_with; pub(crate) mod display; pub(crate) mod eq; -pub(crate) mod layoutable; +pub(crate) mod recipe; pub const DEFAULT_REPR: Repr = Repr::Wgsl; @@ -237,10 +237,8 @@ impl TypeLayout { impl TypeLayout { // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings - pub(crate) fn from_store_ty( - store_type: ir::StoreType, - ) -> Result { - let t: layoutable::LayoutableType = store_type.try_into()?; + pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { + let t: recipe::TypeLayoutRecipe = store_type.try_into()?; Ok(t.layout()) } } @@ -271,7 +269,7 @@ mod tests { use crate::{ any::U32PowerOf2, frontend::rust_types::type_layout::{ - layoutable::{*}, + recipe::{*}, Repr, *, }, }; @@ -279,7 +277,7 @@ mod tests { #[test] fn test_array_alignment() { - let array: LayoutableType = SizedArray::new( + let array: TypeLayoutRecipe = SizedArray::new( Rc::new(Vector::new(ScalarType::F32, Len::X1).into()), NonZeroU32::new(1).unwrap(), ) @@ -314,8 +312,9 @@ mod tests { #[test] fn test_struct_alignment() { - let s = - |repr| -> LayoutableType { SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1), repr).into() }; + let s = |repr| -> TypeLayoutRecipe { + SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1), repr).into() + }; let storage = s(Repr::Wgsl).layout(); let uniform = s(Repr::WgslUniform).layout(); @@ -332,7 +331,7 @@ mod tests { #[test] fn test_nested_struct_field_offset() { - let s = |repr| -> LayoutableType { + let s = |repr| -> TypeLayoutRecipe { let a = SizedStruct::new("A", "a", Vector::new(ScalarType::F32, Len::X1), repr); SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1), repr) .extend("b", a) // offset 4 for storage and packed, offset 16 for uniform @@ -364,7 +363,7 @@ mod tests { #[test] fn test_array_in_struct_field_offset() { - let s = |repr| -> LayoutableType { + let s = |repr| -> TypeLayoutRecipe { SizedStruct::new("B", "a", Vector::new(ScalarType::F32, Len::X1), repr) .extend( "b", @@ -426,7 +425,7 @@ mod tests { }, }, }; - let recipe: LayoutableType = unsized_struct.clone().into(); + let recipe: TypeLayoutRecipe = unsized_struct.clone().into(); let layout = recipe.layout(); assert_eq!(layout.byte_size(), None); @@ -455,7 +454,7 @@ mod tests { // Testing uniform representation unsized_struct.repr = Repr::WgslUniform; - let recipe: LayoutableType = unsized_struct.into(); + let recipe: TypeLayoutRecipe = unsized_struct.into(); println!("{:#?}", recipe); let layout = recipe.layout(); assert_eq!(layout.byte_size(), None); diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs b/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs similarity index 97% rename from shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs rename to shame/src/frontend/rust_types/type_layout/recipe/align_size.rs index 63f66f5..46b2d4a 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs @@ -1,38 +1,38 @@ use super::super::{Repr}; use super::*; -// Size and align of layoutable types // +// Size and align of layout recipe types // // https://www.w3.org/TR/WGSL/#address-space-layout-constraints // pub(crate) const PACKED_ALIGN: U32PowerOf2 = U32PowerOf2::_1; -impl LayoutableType { +impl TypeLayoutRecipe { /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the align. pub fn byte_size(&self, default_repr: Repr) -> Option { match self { - LayoutableType::Sized(s) => Some(s.byte_size(default_repr)), - LayoutableType::UnsizedStruct(_) | LayoutableType::RuntimeSizedArray(_) => None, + TypeLayoutRecipe::Sized(s) => Some(s.byte_size(default_repr)), + TypeLayoutRecipe::UnsizedStruct(_) | TypeLayoutRecipe::RuntimeSizedArray(_) => None, } } /// This is expensive for structs. Prefer `byte_size_and_align` if you also need the size. pub fn align(&self, default_repr: Repr) -> U32PowerOf2 { match self { - LayoutableType::Sized(s) => s.align(default_repr), - LayoutableType::UnsizedStruct(s) => s.align(), - LayoutableType::RuntimeSizedArray(a) => a.align(default_repr), + TypeLayoutRecipe::Sized(s) => s.align(default_repr), + TypeLayoutRecipe::UnsizedStruct(s) => s.align(), + TypeLayoutRecipe::RuntimeSizedArray(a) => a.align(default_repr), } } /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. pub fn byte_size_and_align(&self, default_repr: Repr) -> (Option, U32PowerOf2) { match self { - LayoutableType::Sized(s) => { + TypeLayoutRecipe::Sized(s) => { let (size, align) = s.byte_size_and_align(default_repr); (Some(size), align) } - LayoutableType::UnsizedStruct(s) => (None, s.align()), - LayoutableType::RuntimeSizedArray(a) => (None, a.align(default_repr)), + TypeLayoutRecipe::UnsizedStruct(s) => (None, s.align()), + TypeLayoutRecipe::RuntimeSizedArray(a) => (None, a.align(default_repr)), } } @@ -46,9 +46,9 @@ impl LayoutableType { // Recursively changes all struct reprs to the given `repr`. pub fn change_all_repr(&mut self, repr: Repr) { match self { - LayoutableType::Sized(s) => s.change_all_repr(repr), - LayoutableType::UnsizedStruct(s) => s.change_all_repr(repr), - LayoutableType::RuntimeSizedArray(a) => a.change_all_repr(repr), + TypeLayoutRecipe::Sized(s) => s.change_all_repr(repr), + TypeLayoutRecipe::UnsizedStruct(s) => s.change_all_repr(repr), + TypeLayoutRecipe::RuntimeSizedArray(a) => a.change_all_repr(repr), } } } @@ -783,7 +783,7 @@ mod tests { assert_eq!(mat4x4_f16.align(Repr::Wgsl), U32PowerOf2::_8); assert_eq!(mat4x4_f16.byte_size(Repr::Wgsl), 32); - // Testing Repr::Uniform and Repr::Packed // + // Testing Repr::WgslUniform and Repr::Packed // let scalars = [ ScalarType::F16, diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs b/shame/src/frontend/rust_types/type_layout/recipe/builder.rs similarity index 84% rename from shame/src/frontend/rust_types/type_layout/layoutable/builder.rs rename to shame/src/frontend/rust_types/type_layout/recipe/builder.rs index 5af2662..be1300a 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/builder.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/builder.rs @@ -1,7 +1,7 @@ use super::*; -impl LayoutableType { - /// Fallibly creates a new `LayoutableType` of a struct. +impl TypeLayoutRecipe { + /// Fallibly creates a new `TypeLayoutRecipe` of a struct. /// /// An error is returned if the following rules aren't followed: /// - There must be at least one field. @@ -9,7 +9,7 @@ impl LayoutableType { /// - Only the last field may be unsized (a runtime sized array). pub fn struct_from_parts( struct_name: impl Into, - fields: impl IntoIterator, + fields: impl IntoIterator, repr: Repr, ) -> Result { use StructFromPartsError::*; @@ -23,13 +23,13 @@ impl LayoutableType { .into_iter() .map(|(options, ty)| { Ok(match ty { - LayoutableType::Sized(s) => Field::Sized(SizedField::new(options, s)), - LayoutableType::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( + TypeLayoutRecipe::Sized(s) => Field::Sized(SizedField::new(options, s)), + TypeLayoutRecipe::RuntimeSizedArray(a) => Field::Unsized(RuntimeSizedArrayField::new( options.name, options.custom_min_align, a.element, )), - LayoutableType::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), + TypeLayoutRecipe::UnsizedStruct(_) => return Err(MustNotHaveUnsizedStructField), }) }) .peekable(); @@ -120,9 +120,13 @@ impl SizedStruct { /// Adds either a `SizedType` or a `RuntimeSizedArray` field to the struct. /// - /// Returns a `LayoutableType`, because the `Self` may either stay + /// Returns a `TypeLayoutRecipe`, because the `Self` may either stay /// a `SizedStruct` or become an `UnsizedStruct` depending on the field's type. - pub fn extend_sized_or_array(self, field_options: impl Into, field: SizedOrArray) -> LayoutableType { + pub fn extend_sized_or_array( + self, + field_options: impl Into, + field: SizedOrArray, + ) -> TypeLayoutRecipe { let options = field_options.into(); match field { SizedOrArray::Sized(ty) => self.extend(options, ty).into(), @@ -154,14 +158,14 @@ pub enum SizedOrArray { #[derive(thiserror::Error, Debug)] #[error("`LayoutType` is `UnsizedStruct`, which is not a variant of `SizedOrArray`")] pub struct IsUnsizedStructError; -impl TryFrom for SizedOrArray { +impl TryFrom for SizedOrArray { type Error = IsUnsizedStructError; - fn try_from(value: LayoutableType) -> Result { + fn try_from(value: TypeLayoutRecipe) -> Result { match value { - LayoutableType::Sized(sized) => Ok(SizedOrArray::Sized(sized)), - LayoutableType::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), - LayoutableType::UnsizedStruct(_) => Err(IsUnsizedStructError), + TypeLayoutRecipe::Sized(sized) => Ok(SizedOrArray::Sized(sized)), + TypeLayoutRecipe::RuntimeSizedArray(array) => Ok(SizedOrArray::RuntimeSizedArray(array)), + TypeLayoutRecipe::UnsizedStruct(_) => Err(IsUnsizedStructError), } } } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs similarity index 87% rename from shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs rename to shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs index 3f73021..31d9bda 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs @@ -4,7 +4,7 @@ use super::*; // Conversions to ir types // -/// Errors that can occur when converting IR types to layoutable types. +/// Errors that can occur when converting IR types to recipe types. #[derive(thiserror::Error, Debug)] pub enum IRConversionError { /// Packed vectors do not exist in the shader type system. @@ -82,14 +82,14 @@ fn should_use_color() -> bool { Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages).unwrap_or(false) } -impl TryFrom for ir::StoreType { +impl TryFrom for ir::StoreType { type Error = IRConversionError; - fn try_from(ty: LayoutableType) -> Result { + fn try_from(ty: TypeLayoutRecipe) -> Result { match ty { - LayoutableType::Sized(s) => Ok(ir::StoreType::Sized(s.try_into()?)), - LayoutableType::RuntimeSizedArray(s) => Ok(ir::StoreType::RuntimeSizedArray(s.element.try_into()?)), - LayoutableType::UnsizedStruct(s) => Ok(ir::StoreType::BufferBlock(s.try_into()?)), + TypeLayoutRecipe::Sized(s) => Ok(ir::StoreType::Sized(s.try_into()?)), + TypeLayoutRecipe::RuntimeSizedArray(s) => Ok(ir::StoreType::RuntimeSizedArray(s.element.try_into()?)), + TypeLayoutRecipe::UnsizedStruct(s) => Ok(ir::StoreType::BufferBlock(s.try_into()?)), } } } @@ -224,10 +224,10 @@ impl TryFrom for ir::ir_type::RuntimeSizedArrayField { #[error("Type contains bools, which doesn't have a known layout.")] pub struct ContainsBoolsError; -/// Errors that can occur when converting IR types to layoutable types. +/// Errors that can occur when converting IR types to recipe types. #[allow(missing_docs)] #[derive(thiserror::Error, Debug)] -pub enum LayoutableConversionError { +pub enum RecipeConversionError { #[error("Type contains bools, which don't have a standardized memory layout on the gpu.")] ContainsBool, #[error("Type is a handle, which don't have a standardized memory layout.")] @@ -289,31 +289,31 @@ impl TryFrom for SizedStruct { fields, // TODO(chronicl) hardcoding this is a temporary solution. This whole // TryFrom should be removed in future PRs. - repr: Repr::Storage, + repr: Repr::Wgsl, }) } } -impl From for LayoutableConversionError { +impl From for RecipeConversionError { fn from(_: ContainsBoolsError) -> Self { Self::ContainsBool } } -impl TryFrom for LayoutableType { - type Error = LayoutableConversionError; +impl TryFrom for TypeLayoutRecipe { + type Error = RecipeConversionError; fn try_from(value: ir::StoreType) -> Result { Ok(match value { - ir::StoreType::Sized(sized_type) => LayoutableType::Sized(sized_type.try_into()?), - ir::StoreType::RuntimeSizedArray(element) => LayoutableType::RuntimeSizedArray(RuntimeSizedArray { + ir::StoreType::Sized(sized_type) => TypeLayoutRecipe::Sized(sized_type.try_into()?), + ir::StoreType::RuntimeSizedArray(element) => TypeLayoutRecipe::RuntimeSizedArray(RuntimeSizedArray { element: element.try_into()?, }), ir::StoreType::BufferBlock(buffer_block) => buffer_block.try_into()?, - ir::StoreType::Handle(_) => return Err(LayoutableConversionError::IsHandle), + ir::StoreType::Handle(_) => return Err(RecipeConversionError::IsHandle), }) } } -impl TryFrom for LayoutableType { +impl TryFrom for TypeLayoutRecipe { type Error = ContainsBoolsError; fn try_from(buffer_block: ir::ir_type::BufferBlock) -> Result { @@ -342,7 +342,7 @@ impl TryFrom for LayoutableType { fields: sized_fields, // TODO(chronicl) hardcoding this is a temporary solution. This whole // TryFrom should be removed in future PRs. - repr: Repr::Storage, + repr: Repr::Wgsl, } .into()); }; @@ -353,7 +353,7 @@ impl TryFrom for LayoutableType { last_unsized, // TODO(chronicl) hardcoding this is a temporary solution. This whole // TryFrom should be removed in future PRs. - repr: Repr::Storage, + repr: Repr::Wgsl, } .into()) } @@ -376,7 +376,7 @@ impl From for StructKind { fn test_ir_conversion_error() { use crate::{f32x1, packed::unorm8x2}; - let ty: LayoutableType = SizedStruct::new("A", "a", f32x1::layout_recipe_sized(), Repr::Storage) + let ty: TypeLayoutRecipe = SizedStruct::new("A", "a", f32x1::layout_recipe_sized(), Repr::Wgsl) .extend("b", f32x1::layout_recipe_sized()) .extend("a", f32x1::layout_recipe_sized()) .into(); @@ -391,7 +391,7 @@ fn test_ir_conversion_error() { })) )); - let ty: LayoutableType = SizedStruct::new("A", "a", unorm8x2::layout_recipe_sized(), Repr::Storage).into(); + let ty: TypeLayoutRecipe = SizedStruct::new("A", "a", unorm8x2::layout_recipe_sized(), Repr::Wgsl).into(); let result: Result = ty.try_into(); assert!(matches!(result, Err(IRConversionError::ContainsPackedVector))); } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs b/shame/src/frontend/rust_types/type_layout/recipe/mod.rs similarity index 80% rename from shame/src/frontend/rust_types/type_layout/layoutable/mod.rs rename to shame/src/frontend/rust_types/type_layout/recipe/mod.rs index fc9c841..04c349e 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/mod.rs @@ -21,14 +21,12 @@ pub(crate) mod to_layout; pub use align_size::{FieldOffsets, MatrixMajor, StructLayoutCalculator, array_size, array_stride, array_align}; pub use builder::{SizedOrArray, FieldOptions}; -/// Types that can be layed out in memory. +/// `TypeLayoutRecipe` describes how a type should be laid out in memory. /// -/// `LayoutableType` does not contain any layout information itself, but a layout -/// can be assigned to it using [`GpuTypeLayout`] according to one of the available layout rules: -/// `repr::Storage`, `repr::Uniform`` or `repr::Packed`, see [`GpuTypeLayout`] documentation -/// for more details. +/// It does not contain any layout information itself, but can be converted to a `TypeLayout` +/// using the `TypeLayoutRecipe::layout` method. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum LayoutableType { +pub enum TypeLayoutRecipe { /// A type with a known size. Sized(SizedType), /// A struct with a runtime sized array as it's last field. @@ -140,22 +138,13 @@ pub struct RuntimeSizedArrayField { pub array: RuntimeSizedArray, } -// Conversions to ScalarType, SizedType and LayoutableType // +// Conversions to ScalarType, SizedType and TypeLayoutRecipe // macro_rules! impl_into_sized_type { ($($ty:ident -> $variant:path),*) => { $( - impl $ty { - /// Const conversion to [`SizedType`] - pub const fn into_sized_type(self) -> SizedType { $variant(self) } - /// Const conversion to [`LayoutableType`] - pub const fn into_layoutable_type(self) -> LayoutableType { - LayoutableType::Sized(self.into_sized_type()) - } - } - impl From<$ty> for SizedType { - fn from(v: $ty) -> Self { v.into_sized_type() } + fn from(v: $ty) -> Self { $variant(v) } } )* }; @@ -169,18 +158,18 @@ impl_into_sized_type!( PackedVector -> SizedType::PackedVec ); -impl From for LayoutableType +impl From for TypeLayoutRecipe where SizedType: From, { - fn from(value: T) -> Self { LayoutableType::Sized(SizedType::from(value)) } + fn from(value: T) -> Self { TypeLayoutRecipe::Sized(SizedType::from(value)) } } -impl From for LayoutableType { - fn from(s: UnsizedStruct) -> Self { LayoutableType::UnsizedStruct(s) } +impl From for TypeLayoutRecipe { + fn from(s: UnsizedStruct) -> Self { TypeLayoutRecipe::UnsizedStruct(s) } } -impl From for LayoutableType { - fn from(a: RuntimeSizedArray) -> Self { LayoutableType::RuntimeSizedArray(a) } +impl From for TypeLayoutRecipe { + fn from(a: RuntimeSizedArray) -> Self { TypeLayoutRecipe::RuntimeSizedArray(a) } } impl ScalarTypeInteger { @@ -209,12 +198,12 @@ impl From for ScalarType { // Display impls -impl std::fmt::Display for LayoutableType { +impl std::fmt::Display for TypeLayoutRecipe { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - LayoutableType::Sized(s) => s.fmt(f), - LayoutableType::RuntimeSizedArray(a) => a.fmt(f), - LayoutableType::UnsizedStruct(s) => s.fmt(f), + TypeLayoutRecipe::Sized(s) => s.fmt(f), + TypeLayoutRecipe::RuntimeSizedArray(a) => a.fmt(f), + TypeLayoutRecipe::UnsizedStruct(s) => s.fmt(f), } } } diff --git a/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs similarity index 90% rename from shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs rename to shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs index ffc9d98..7392f91 100644 --- a/shame/src/frontend/rust_types/type_layout/layoutable/to_layout.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs @@ -6,18 +6,18 @@ use crate::{ ir, TypeLayout, }; use super::{ - Atomic, LayoutableType, Matrix, PackedVector, RuntimeSizedArray, SizedArray, SizedField, SizedStruct, SizedType, + Atomic, TypeLayoutRecipe, Matrix, PackedVector, RuntimeSizedArray, SizedArray, SizedField, SizedStruct, SizedType, UnsizedStruct, Vector, }; -impl LayoutableType { +impl TypeLayoutRecipe { pub fn layout(&self) -> TypeLayout { self.layout_with_default_repr(DEFAULT_REPR) } pub fn layout_with_default_repr(&self, default_repr: Repr) -> TypeLayout { match self { - LayoutableType::Sized(ty) => ty.layout(default_repr), - LayoutableType::UnsizedStruct(ty) => ty.layout().into(), - LayoutableType::RuntimeSizedArray(ty) => ty.layout(default_repr).into(), + TypeLayoutRecipe::Sized(ty) => ty.layout(default_repr), + TypeLayoutRecipe::UnsizedStruct(ty) => ty.layout().into(), + TypeLayoutRecipe::RuntimeSizedArray(ty) => ty.layout(default_repr).into(), } } } diff --git a/shame/src/frontend/rust_types/vec.rs b/shame/src/frontend/rust_types/vec.rs index 5906a07..d06b34b 100644 --- a/shame/src/frontend/rust_types/vec.rs +++ b/shame/src/frontend/rust_types/vec.rs @@ -576,7 +576,7 @@ impl GpuLayout for vec where vec: NoBools, { - fn layout_recipe() -> layout::LayoutableType { + fn layout_recipe() -> layout::TypeLayoutRecipe { layout::Vector::new( T::SCALAR_TYPE .try_into() diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index 85683e6..f89c490 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, num::NonZeroU32}; use crate::{ any::{U32PowerOf2}, common::floating_point::{f16, f32_eq_where_nans_are_equal, f64_eq_where_nans_are_equal}, - frontend::rust_types::type_layout::{self, layoutable::align_size::PACKED_ALIGN, Repr}, + frontend::rust_types::type_layout::{self, recipe::align_size::PACKED_ALIGN, Repr}, ir::Comp4, }; diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index d28b5c0..c5ba67a 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -33,7 +33,7 @@ use crate::{ error::InternalError, rust_types::{ len::x3, - type_layout::{self, layoutable, StructLayout, DEFAULT_REPR}, + type_layout::{self, recipe, StructLayout, DEFAULT_REPR}, }, }, ir::{ @@ -353,7 +353,7 @@ impl WipPushConstantsField { let byte_size = sized_struct.byte_size(); // TODO(release) the `.expect()` calls here can be removed by building a `std::alloc::Layout`-like builder for struct layouts. - let sized_struct: layoutable::SizedStruct = sized_struct + let sized_struct: recipe::SizedStruct = sized_struct .try_into() .map_err(|e| InternalError::new(true, format!("{e}")))?; let layout = sized_struct.layout(DEFAULT_REPR); diff --git a/shame/src/lib.rs b/shame/src/lib.rs index c585be2..6b9223c 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -479,38 +479,38 @@ pub mod any { pub use type_layout::StructLayout; pub use type_layout::FieldLayout; - // layoutable types - pub use type_layout::layoutable::LayoutableType; - pub use type_layout::layoutable::UnsizedStruct; - pub use type_layout::layoutable::RuntimeSizedArray; - pub use type_layout::layoutable::SizedType; - pub use type_layout::layoutable::Vector; - pub use type_layout::layoutable::Matrix; - pub use type_layout::layoutable::MatrixMajor; - pub use type_layout::layoutable::SizedArray; - pub use type_layout::layoutable::Atomic; - pub use type_layout::layoutable::PackedVector; - pub use type_layout::layoutable::SizedStruct; - - // layoutable type parts - pub use type_layout::layoutable::ScalarType; - pub use type_layout::layoutable::ScalarTypeFp; - pub use type_layout::layoutable::ScalarTypeInteger; - pub use type_layout::layoutable::Len; - pub use type_layout::layoutable::Len2; - pub use type_layout::layoutable::SizedField; - pub use type_layout::layoutable::RuntimeSizedArrayField; - pub use type_layout::layoutable::CanonName; - pub use type_layout::layoutable::SizedOrArray; - pub use type_layout::layoutable::FieldOptions; + // recipe types + pub use type_layout::recipe::TypeLayoutRecipe; + pub use type_layout::recipe::UnsizedStruct; + pub use type_layout::recipe::RuntimeSizedArray; + pub use type_layout::recipe::SizedType; + pub use type_layout::recipe::Vector; + pub use type_layout::recipe::Matrix; + pub use type_layout::recipe::MatrixMajor; + pub use type_layout::recipe::SizedArray; + pub use type_layout::recipe::Atomic; + pub use type_layout::recipe::PackedVector; + pub use type_layout::recipe::SizedStruct; + + // recipe type parts + pub use type_layout::recipe::ScalarType; + pub use type_layout::recipe::ScalarTypeFp; + pub use type_layout::recipe::ScalarTypeInteger; + pub use type_layout::recipe::Len; + pub use type_layout::recipe::Len2; + pub use type_layout::recipe::SizedField; + pub use type_layout::recipe::RuntimeSizedArrayField; + pub use type_layout::recipe::CanonName; + pub use type_layout::recipe::SizedOrArray; + pub use type_layout::recipe::FieldOptions; // layout calculation utility - pub use type_layout::layoutable::StructLayoutCalculator; - pub use type_layout::layoutable::FieldOffsets; + pub use type_layout::recipe::StructLayoutCalculator; + pub use type_layout::recipe::FieldOffsets; // conversion and builder errors - pub use type_layout::layoutable::builder::IsUnsizedStructError; - pub use type_layout::layoutable::builder::StructFromPartsError; + pub use type_layout::recipe::builder::IsUnsizedStructError; + pub use type_layout::recipe::builder::StructFromPartsError; } // runtime binding api diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index c6dd188..0b202ea 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -87,7 +87,7 @@ pub fn impl_for_struct( if let (Some((span, _)), WhichDerive::CpuLayout) = (&gpu_repr, &which_derive) { bail!(*span, "`gpu_repr` attribute is only supported by `derive(GpuLayout)`") } - // if no `#[gpu_repr(_)]` attribute was explicitly specified, we default to `Repr::Storage` + // if no `#[gpu_repr(_)]` attribute was explicitly specified, we default to `Repr::Wgsl` let gpu_repr = gpu_repr.map(|(_, repr)| repr).unwrap_or(util::Repr::Wgsl); let gpu_repr_shame = match gpu_repr { Repr::Packed => quote!( #re::Repr::Packed ), @@ -227,8 +227,8 @@ pub fn impl_for_struct( #last_field_type: #re::NoBools + #re::NoHandles + #re::GpuLayout, #where_clause_predicates { - fn layout_recipe() -> #re::LayoutableType { - let result = #re::LayoutableType::struct_from_parts( + fn layout_recipe() -> #re::TypeLayoutRecipe { + let result = #re::TypeLayoutRecipe::struct_from_parts( std::stringify!(#derive_struct_ident), [ #(( @@ -244,7 +244,7 @@ pub fn impl_for_struct( ); match result { - Ok(layoutable_type) => layoutable_type, + Ok(recipe_type) => recipe_type, Err(#re::StructFromPartsError::MustHaveAtLeastOneField) => unreachable!("checked above"), Err(#re::StructFromPartsError::OnlyLastFieldMayBeUnsized) => unreachable!("ensured by field trait bounds"), // GpuType is not implemented for derived structs directly, so they can't be used From 519b6622c3f5d591953bff55ddae38e32c9da494 Mon Sep 17 00:00:00 2001 From: chronicl Date: Wed, 23 Jul 2025 16:24:18 +0200 Subject: [PATCH 142/182] cleanup --- shame/src/frontend/rust_types/type_layout/mod.rs | 9 +-------- .../frontend/rust_types/type_layout/recipe/ir_compat.rs | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 7a7f8f4..fc0373c 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -229,13 +229,6 @@ impl TypeLayout { } } - pub(crate) fn first_line_of_display_with_ellipsis(&self) -> String { - let string = format!("{}", self); - string.split_once('\n').map(|(s, _)| format!("{s}…")).unwrap_or(string) - } -} - -impl TypeLayout { // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { let t: recipe::TypeLayoutRecipe = store_type.try_into()?; @@ -455,7 +448,7 @@ mod tests { // Testing uniform representation unsized_struct.repr = Repr::WgslUniform; let recipe: TypeLayoutRecipe = unsized_struct.into(); - println!("{:#?}", recipe); + println!("{recipe:#?}"); let layout = recipe.layout(); assert_eq!(layout.byte_size(), None); // Struct alignmment has to be a multiple of 16, but the runtime sized array diff --git a/shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs b/shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs index 31d9bda..329a68b 100644 --- a/shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/ir_compat.rs @@ -59,7 +59,7 @@ impl std::fmt::Display for DuplicateFieldNameError { Field names must be unique in the shader type system.\n\ The following struct contains duplicate field names:" )?; - let header = writeln!(f, "struct {} {{", struct_name); + let header = writeln!(f, "struct {struct_name} {{"); for (i, field) in sized_fields.iter().enumerate() { color(f, i)?; writeln!(f, "{indent}{}: {},{}", field.name, field.ty, arrow(i))?; From 23981f22db5874a62bb45ca5487d73823d0e9bb6 Mon Sep 17 00:00:00 2001 From: chronicl Date: Wed, 23 Jul 2025 17:52:21 +0200 Subject: [PATCH 143/182] layout mismatch cleanup --- .../src/frontend/rust_types/layout_traits.rs | 5 +- .../rust_types/type_layout/compatible_with.rs | 239 +----------------- .../src/frontend/rust_types/type_layout/eq.rs | 226 +++++++++++++++-- shame/src/ir/ir_type/layout_constraints.rs | 4 +- 4 files changed, 221 insertions(+), 253 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 24332d0..1e5643c 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -136,9 +136,10 @@ use std::rc::Rc; /// [`StorageTexture`]: crate::StorageTexture /// pub trait GpuLayout { - /// TODO(chronicl) + /// Returns a [`TypeLayoutRecipe`] that describes how the type is laid out in memory. fn layout_recipe() -> TypeLayoutRecipe; - /// TODO(chronicl) + + /// For `GpuSized` types, this returns the [`SizedType`] that describes the type's layout. fn layout_recipe_sized() -> SizedType where Self: GpuSized, diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 7febede..ef43823 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -4,7 +4,11 @@ use crate::{ any::layout::StructLayout, call_info, common::prettify::{set_color, UnwrapOrStr}, - frontend::rust_types::type_layout::{display::LayoutInfo, ArrayLayout}, + frontend::rust_types::type_layout::{ + display::LayoutInfo, + eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, + ArrayLayout, + }, ir::{ir_type::max_u64_po2_dividing, recording::Context}, TypeLayout, }; @@ -24,7 +28,9 @@ use super::{recipe::TypeLayoutRecipe, Repr}; /// Wgsl has only one representation of types - there is no choice between std140 and std430 /// like in glsl - so to be representable in wgsl means that the type layout produced by /// the recipe is the same as the one produced by the same recipe but with all structs -/// in the recipe using Repr::Wgsl, which is what shame calls wgsl's representation/layout algorithm. +/// in the recipe using the Repr::Wgsl layout algorithm. Aditionally, all custom attributes used +/// by the recipe need to be support by wgsl, which are only the struct field attributes +/// `#[align(N)]` and `#[size(N)]` currently. pub struct TypeLayoutCompatibleWith { recipe: TypeLayoutRecipe, _phantom: std::marker::PhantomData, @@ -46,7 +52,9 @@ impl TypeLayoutCompatibleWith { AddressSpaceEnum::WgslStorage | AddressSpaceEnum::WgslUniform => { // Wgsl has only one type representation: Repr::Wgsl, so the layout produced by the recipe // is representable in wgsl iff the layout produced by the same recipe but with - // all structs in the recipe using Repr::Wgsl is the same. + // all structs in the recipe using Repr::Wgsl is the same and all custom attributes + // used by the recipe are supported by wgsl, which is checked in `TypeLayoutRecipe::layout` + // TODO(chronicl) line above let recipe_unified = recipe.to_unified_repr(Repr::Wgsl); let layout_unified = recipe_unified.layout(); if layout != layout_unified { @@ -351,228 +359,3 @@ where Ok(()) } - -/// Contains information about the layout mismatch between two `TypeLayout`s. -/// -/// The type layouts are traversed depth-first and the first mismatch encountered -/// is reported at its deepest level. -/// -/// In case of nested structs this means that if the field `a` in -/// ``` -/// struct A { -/// a: struct B { ... } -/// } -/// struct AOther { -/// a: struct BOther { ... } -/// } -/// ``` -/// mismatches, because `B` and `BOther` don't match, then the exact mismatch -/// between `B` and `BOther` is reported and not the field mismatch of `a` in `A` and `AOther`. -/// -/// Nested arrays are reported in two levels: `LayoutMismatch::TopLevel` contains the -/// top level / outer most array layout and a `TopLevelMismatch`, which contains the -/// inner type layout where the mismatch is happening. -/// For example if there is an array stride mismatch of the inner array of `Array>`, -/// then `LayoutMismatch::TopLevel` contains the layout of `Array>` and -/// a `TopLevelMismatch::ArrayStride` with the layout of `Array`. -/// -/// A field of nested arrays in a struct is handled in the same way by -/// `LayoutMismatch::Struct` containing the field index, which let's us access the outer -/// most array, and a `TopLevelMismatch`, which let's us access the inner type layout -/// where the mismatch is happening. -#[derive(Debug, Clone)] -pub enum LayoutMismatch { - TopLevel { - layout_left: TypeLayout, - layout_right: TypeLayout, - mismatch: TopLevelMismatch, - }, - Struct { - struct_left: StructLayout, - struct_right: StructLayout, - mismatch: StructMismatch, - }, -} - -#[derive(Debug, Clone)] -pub enum TopLevelMismatch { - Type, - ByteSize { - left: TypeLayout, - right: TypeLayout, - }, - ArrayStride { - array_left: ArrayLayout, - array_right: ArrayLayout, - }, -} - -/// Field count is checked last. -#[derive(Debug, Clone)] -pub enum StructMismatch { - FieldName { - field_index: usize, - }, - FieldLayout { - field_index: usize, - mismatch: TopLevelMismatch, - }, - FieldOffset { - field_index: usize, - }, - FieldCount, -} - -/// Find the first depth first layout mismatch -pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { - use TypeLayout::*; - - // First check if the kinds are the same type - match (&layout1, &layout2) { - (Vector(v1), Vector(v2)) => { - if v1.ty != v2.ty { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::Type, - }); - } - } - (PackedVector(p1), PackedVector(p2)) => { - if p1.ty != p2.ty { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::Type, - }); - } - } - (Matrix(m1), Matrix(m2)) => { - if m1.ty != m2.ty { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::Type, - }); - } - } - (Array(a1), Array(a2)) => { - // Recursively check element types - match try_find_mismatch(&a1.element_ty, &a2.element_ty) { - // Update the top level layouts and propagate the LayoutMismatch - Some(LayoutMismatch::TopLevel { - layout_left: layout1, - layout_right: layout2, - mismatch, - }) => { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch, - }); - } - // Struct mismatch, so it's not a top-level mismatch anymore - m @ Some(LayoutMismatch::Struct { .. }) => return m, - None => return None, - } - - // Check array sizes match - if a1.len != a2.len { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::Type, - }); - } - - // Check array stride - if a1.byte_stride != a2.byte_stride { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::ArrayStride { - array_left: (**a1).clone(), - array_right: (**a2).clone(), - }, - }); - } - } - (Struct(s1), Struct(s2)) => { - return try_find_struct_mismatch(s1, s2); - } - // Different kinds entirely. Matching exhaustively, so that changes to TypeLayout lead us here. - (Vector(_) | PackedVector(_) | Matrix(_) | Array(_) | Struct(_), _) => { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::Type, - }); - } - } - - // Check byte size - if layout1.byte_size() != layout2.byte_size() { - return Some(LayoutMismatch::TopLevel { - layout_left: layout1.clone(), - layout_right: layout2.clone(), - mismatch: TopLevelMismatch::ByteSize { - left: layout1.clone(), - right: layout2.clone(), - }, - }); - } - - None -} - -fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> Option { - for (field_index, (field1, field2)) in struct1.fields.iter().zip(struct2.fields.iter()).enumerate() { - // Order of checks is important here. We check in order - // - field name - // - field type, byte size and if the field is/contains a struct, recursively check its fields - // - field offset - if field1.name != field2.name { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldName { field_index }, - }); - } - - // Recursively check field types - if let Some(inner_mismatch) = try_find_mismatch(&field1.ty, &field2.ty) { - match inner_mismatch { - // If it's a top-level mismatch, convert it to a field mismatch - LayoutMismatch::TopLevel { mismatch, .. } => { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldLayout { field_index, mismatch }, - }); - } - // Pass through nested struct mismatches - struct_mismatch @ LayoutMismatch::Struct { .. } => return Some(struct_mismatch), - } - } - - // Check field offset - if field1.rel_byte_offset != field2.rel_byte_offset { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldOffset { field_index }, - }); - } - } - - // Check field count - if struct1.fields.len() != struct2.fields.len() { - return Some(LayoutMismatch::Struct { - struct_left: struct1.clone(), - struct_right: struct2.clone(), - mismatch: StructMismatch::FieldCount, - }); - } - - None -} diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index 0c48098..cc66fa0 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,30 +1,213 @@ use crate::common::prettify::UnwrapOrStr; -use crate::frontend::rust_types::type_layout::compatible_with::{StructMismatch, TopLevelMismatch}; use crate::frontend::rust_types::type_layout::display::LayoutInfo; -use super::compatible_with::try_find_mismatch; use super::*; +/// Contains information about the layout mismatch between two `TypeLayout`s. +/// +/// The type layouts are traversed depth-first and the first mismatch encountered +/// is reported at its deepest level. +/// +/// In case of nested structs this means that if the field `a` in +/// ``` +/// struct A { +/// a: struct B { ... } +/// } +/// struct AOther { +/// a: struct BOther { ... } +/// } +/// ``` +/// mismatches, because `B` and `BOther` don't match, then the exact mismatch +/// between `B` and `BOther` is reported and not the field mismatch of `a` in `A` and `AOther`. +/// +/// Nested arrays are reported in two levels: `LayoutMismatch::TopLevel` contains the +/// top level / outer most array layout and a `TopLevelMismatch`, which contains the +/// inner type layout where the mismatch is happening. +/// For example if there is an array stride mismatch of the inner array of `Array>`, +/// then `LayoutMismatch::TopLevel` contains the layout of `Array>` and +/// a `TopLevelMismatch::ArrayStride` with the layout of `Array`. +/// +/// A field of nested arrays in a struct is handled in the same way by +/// `LayoutMismatch::Struct` containing the field index, which let's us access the outer +/// most array, and a `TopLevelMismatch`, which let's us access the inner type layout +/// where the mismatch is happening. +#[derive(Debug, Clone)] +pub enum LayoutMismatch { + TopLevel { + layout_left: TypeLayout, + layout_right: TypeLayout, + mismatch: TopLevelMismatch, + }, + Struct { + struct_left: StructLayout, + struct_right: StructLayout, + mismatch: StructMismatch, + }, +} + +#[derive(Debug, Clone)] +pub enum TopLevelMismatch { + Type, + ByteSize { + left: TypeLayout, + right: TypeLayout, + }, + ArrayStride { + array_left: ArrayLayout, + array_right: ArrayLayout, + }, +} + +/// Field count is checked last. +#[derive(Debug, Clone)] +pub enum StructMismatch { + FieldName { + field_index: usize, + }, + FieldLayout { + field_index: usize, + mismatch: TopLevelMismatch, + }, + FieldOffset { + field_index: usize, + }, + FieldCount, +} + +/// Find the first depth first layout mismatch +pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> Option { + use TypeLayout::*; + + let make_mismatch = |mismatch: TopLevelMismatch| LayoutMismatch::TopLevel { + layout_left: layout1.clone(), + layout_right: layout2.clone(), + mismatch, + }; + + // First check if the kinds are the same type + match (&layout1, &layout2) { + (Vector(v1), Vector(v2)) => { + if v1.ty != v2.ty { + return Some(make_mismatch(TopLevelMismatch::Type)); + } + } + (PackedVector(p1), PackedVector(p2)) => { + if p1.ty != p2.ty { + return Some(make_mismatch(TopLevelMismatch::Type)); + } + } + (Matrix(m1), Matrix(m2)) => { + if m1.ty != m2.ty { + return Some(make_mismatch(TopLevelMismatch::Type)); + } + } + (Array(a1), Array(a2)) => { + // Recursively check element types + match try_find_mismatch(&a1.element_ty, &a2.element_ty) { + // Update the top level layouts and propagate the LayoutMismatch + Some(LayoutMismatch::TopLevel { mismatch, .. }) => { + return Some(make_mismatch(mismatch)); + } + // Struct mismatch, so it's not a top-level mismatch anymore + m @ Some(LayoutMismatch::Struct { .. }) => return m, + None => return None, + } + + // Check array sizes match + if a1.len != a2.len { + return Some(make_mismatch(TopLevelMismatch::Type)); + } + + // Check array stride + if a1.byte_stride != a2.byte_stride { + return Some(make_mismatch(TopLevelMismatch::ArrayStride { + array_left: (**a1).clone(), + array_right: (**a2).clone(), + })); + } + } + (Struct(s1), Struct(s2)) => { + return try_find_struct_mismatch(s1, s2); + } + // Different kinds entirely. Matching exhaustively, so that changes to TypeLayout lead us here. + (Vector(_) | PackedVector(_) | Matrix(_) | Array(_) | Struct(_), _) => { + return Some(make_mismatch(TopLevelMismatch::Type)); + } + } + + // Check byte size + if layout1.byte_size() != layout2.byte_size() { + return Some(make_mismatch(TopLevelMismatch::ByteSize { + left: layout1.clone(), + right: layout2.clone(), + })); + } + + None +} + +fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> Option { + let make_mismatch = |mismatch: StructMismatch| LayoutMismatch::Struct { + struct_left: struct1.clone(), + struct_right: struct2.clone(), + mismatch, + }; + + for (field_index, (field1, field2)) in struct1.fields.iter().zip(struct2.fields.iter()).enumerate() { + // Order of checks is important here. We check in order + // - field name + // - field type, byte size and if the field is/contains a struct, recursively check its fields + // - field offset + if field1.name != field2.name { + return Some(make_mismatch(StructMismatch::FieldName { field_index })); + } + + // Recursively check field types + if let Some(inner_mismatch) = try_find_mismatch(&field1.ty, &field2.ty) { + match inner_mismatch { + // If it's a top-level mismatch, convert it to a field mismatch + LayoutMismatch::TopLevel { mismatch, .. } => { + return Some(make_mismatch(StructMismatch::FieldLayout { field_index, mismatch })); + } + // Pass through nested struct mismatches + struct_mismatch @ LayoutMismatch::Struct { .. } => return Some(struct_mismatch), + } + } + + // Check field offset + if field1.rel_byte_offset != field2.rel_byte_offset { + return Some(make_mismatch(StructMismatch::FieldOffset { field_index })); + } + } + + // Check field count + if struct1.fields.len() != struct2.fields.len() { + return Some(make_mismatch(StructMismatch::FieldCount)); + } + + None +} + /// Error of two layouts mismatching. Implements Display for a visualization of the mismatch. #[derive(Clone)] -pub struct LayoutMismatch { +pub struct CheckEqLayoutMismatch { /// 2 (name, layout) pairs layouts: [(String, TypeLayout); 2], colored_error: bool, } - -impl std::fmt::Debug for LayoutMismatch { +impl std::fmt::Debug for CheckEqLayoutMismatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{self}") } } -impl Display for LayoutMismatch { +impl Display for CheckEqLayoutMismatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let [(a_name, a), (b_name, b)] = &self.layouts; let layouts = [(a_name.as_str(), a), (b_name.as_str(), b)]; - match LayoutMismatch::write(f, layouts, self.colored_error)? { - Mismatch::Found => {} - Mismatch::NotFound => { + match CheckEqLayoutMismatch::write(f, layouts, self.colored_error) { + Ok(()) => {} + Err(DisplayMismatchError::FmtError(e)) => return Err(e), + Err(DisplayMismatchError::NotFound) => { writeln!( f, "" @@ -41,19 +224,21 @@ impl Display for LayoutMismatch { } } -/// Whether the mismatch was found or not. -pub(crate) enum Mismatch { - Found, +pub(crate) enum DisplayMismatchError { NotFound, + FmtError(std::fmt::Error), +} +impl From for DisplayMismatchError { + fn from(err: std::fmt::Error) -> Self { DisplayMismatchError::FmtError(err) } } -impl LayoutMismatch { +impl CheckEqLayoutMismatch { #[allow(clippy::needless_return)] pub(crate) fn write( f: &mut W, layouts: [(&str, &TypeLayout); 2], colored: bool, - ) -> Result { + ) -> Result<(), DisplayMismatchError> { let [(a_name, a), (b_name, b)] = layouts; let use_256_color_mode = false; @@ -70,12 +255,11 @@ impl LayoutMismatch { // Using try_find_mismatch to find the first mismatching type / struct field. let Some(mismatch) = try_find_mismatch(a, b) else { - return Ok(Mismatch::NotFound); + return Err(DisplayMismatchError::NotFound); }; - use compatible_with::LayoutMismatch::{TopLevel, Struct}; match mismatch { - TopLevel { + LayoutMismatch::TopLevel { layout_left, layout_right, mismatch, @@ -122,7 +306,7 @@ impl LayoutMismatch { )?; } }, - Struct { + LayoutMismatch::Struct { struct_left, struct_right, mismatch, @@ -307,7 +491,7 @@ impl LayoutMismatch { } } - Ok(Mismatch::Found) + Ok(()) } } @@ -315,13 +499,13 @@ impl LayoutMismatch { /// /// if the two layouts are not equal it uses the debug names in the returned /// error to tell the two layouts apart. -pub(crate) fn check_eq(a: (&str, &TypeLayout), b: (&str, &TypeLayout)) -> Result<(), LayoutMismatch> +pub(crate) fn check_eq(a: (&str, &TypeLayout), b: (&str, &TypeLayout)) -> Result<(), CheckEqLayoutMismatch> where TypeLayout: PartialEq, { match a.1 == b.1 { true => Ok(()), - false => Err(LayoutMismatch { + false => Err(CheckEqLayoutMismatch { layouts: [(a.0.into(), a.1.to_owned()), (b.0.into(), b.1.to_owned())], colored_error: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) .unwrap_or(false), diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index e5d605a..3202501 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -6,7 +6,7 @@ use std::{ use thiserror::Error; -use crate::frontend::rust_types::type_layout::{display::LayoutInfo, eq::LayoutMismatch, TypeLayout}; +use crate::frontend::rust_types::type_layout::{display::LayoutInfo, eq::CheckEqLayoutMismatch, TypeLayout}; use crate::{ backend::language::Language, call_info, @@ -468,7 +468,7 @@ Type `{}` contains type `{struct_or_block_name}` which has a custom byte-alignme #[error("custom size of {custom} is too small. `{ty}` must have a size of at least {required}")] CustomSizeTooSmall { custom: u64, required: u64, ty: Type }, #[error("memory layout mismatch:\n{0}\n{}", if let Some(comment) = .1 {comment.as_str()} else {""})] - LayoutMismatch(LayoutMismatch, Option), + LayoutMismatch(CheckEqLayoutMismatch, Option), #[error("runtime-sized type {name} cannot be element in an array buffer")] UnsizedStride { name: String }, #[error( From 8b36dd4200be0aa12b259c5449fdbdf6a24723b4 Mon Sep 17 00:00:00 2001 From: chronicl Date: Wed, 23 Jul 2025 18:35:50 +0200 Subject: [PATCH 144/182] layout mismatch test prints --- .../rust_types/type_layout/compatible_with.rs | 4 +- .../src/frontend/rust_types/type_layout/eq.rs | 93 +++++++++++++++++-- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index ef43823..869c6ab 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -146,10 +146,10 @@ impl AddressSpaceEnum { #[derive(thiserror::Error, Debug, Clone)] pub enum AddressSpaceError { - #[error("Address space requirements not satisfied:\n{0}")] - DoesntSatisfyRequirements(LayoutError), #[error("{} is not representable in {}:\n{0}", .0.recipe, .0.address_space.language())] NotRepresentable(LayoutError), + #[error("Address space requirements not satisfied:\n{0}")] + DoesntSatisfyRequirements(LayoutError), #[error("Unknown layout error occured for {0} in {1}.")] UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), #[error( diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index cc66fa0..c0ec967 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -110,7 +110,7 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } // Struct mismatch, so it's not a top-level mismatch anymore m @ Some(LayoutMismatch::Struct { .. }) => return m, - None => return None, + None => {} } // Check array sizes match @@ -266,7 +266,7 @@ impl CheckEqLayoutMismatch { } => match mismatch { TopLevelMismatch::Type => writeln!( f, - "The layouts of `{}` and `{}` do not match, because their types are semantically different.", + "The layouts of `{}` ({a_name}) and `{}` ({b_name}) do not match, because their types are semantically different.", layout_left.short_name(), layout_right.short_name() )?, @@ -276,13 +276,13 @@ impl CheckEqLayoutMismatch { } => { writeln!( f, - "The layouts of `{}` and `{}` do not match.", + "The layouts of `{}` ({a_name}) and `{}` ({b_name}) do not match.", layout_left.short_name(), layout_right.short_name() )?; writeln!( f, - "`{}` has a stride of {}, while `{}` has a stride of {}.", + "`{}` ({a_name}) has a stride of {}, while `{}` ({b_name}) has a stride of {}.", array_left.short_name(), array_left.byte_stride, array_right.short_name(), @@ -292,13 +292,13 @@ impl CheckEqLayoutMismatch { TopLevelMismatch::ByteSize { left, right } => { writeln!( f, - "The layouts of `{}` and `{}` do not match.", + "The layouts of `{}` ({a_name}) and `{}` ({b_name}) do not match.", layout_left.short_name(), layout_right.short_name() )?; writeln!( f, - "`{}` has a byte size of {}, while `{}` has a byte size of {}.", + "`{}` ({a_name}) has a byte size of {}, while `{}` ({b_name}) has a byte size of {}.", left.short_name(), UnwrapOrStr(left.byte_size(), "runtime-sized"), right.short_name(), @@ -522,7 +522,7 @@ mod tests { #[derive(Clone, Copy)] #[repr(C)] - struct f32x3_align4(pub [f32; 4]); + struct f32x3_align4(pub [f32; 3]); impl CpuLayout for f32x3_align4 { fn cpu_layout() -> shame::TypeLayout { @@ -532,6 +532,18 @@ mod tests { } } + #[derive(Clone, Copy)] + #[repr(C)] + struct f32x3_size16(pub [f32; 4]); + + impl CpuLayout for f32x3_size16 { + fn cpu_layout() -> shame::TypeLayout { + let mut layout = gpu_layout::(); + layout.set_byte_size(Some(16)); + layout + } + } + fn print_mismatch() { println!( "{}", @@ -558,14 +570,17 @@ mod tests { print_mismatch::(); // field type mismatch + println!("The next one also shows how \"...\" is used if there are more fields after the mismatching field\n"); #[derive(GpuLayout)] pub struct B { a: f32x1, + b: f32x1, } #[derive(CpuLayout)] #[repr(C)] pub struct BCpu { a: u32, + b: f32, } print_mismatch::(); @@ -582,5 +597,69 @@ mod tests { b: f32x3_align4, } print_mismatch::(); + + // field byte size mismatch + #[derive(GpuLayout)] + pub struct D { + a: f32x3, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct DCpu { + a: f32x3_size16, + } + print_mismatch::(); + + // field nested byte size mismatch + println!( + "The next one does not show the `size` column, because it could be confusing, since the type in the array is where the mismatch happens:\n" + ); + #[derive(GpuLayout)] + pub struct E { + a: sm::Array>, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct ECpu { + a: [f32x3_size16; 4], + } + print_mismatch::(); + + // field stride mismatch + #[derive(GpuLayout)] + pub struct F { + a: sm::Array>, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct FCpu { + a: [f32x3_align4; 4], + } + print_mismatch::(); + + println!( + "The next two error messages are what is produced when non-structs, in this case arrays, are the top level types\n" + ); + + // stride mismatch + print_mismatch::>, [f32x3_align4; 4]>(); + + // nested stride mismatch + print_mismatch::>, sm::Size<2>>, [[f32x3_align4; 4]; 2]>(); + + // nested stride in struct mismatch + println!( + "The next one does not show the `stride` column, because it could be confusing, since the type in the array is where the mismatch happens:\n" + ); + #[derive(GpuLayout)] + pub struct G { + a: sm::Array>, sm::Size<2>>, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct GCpu { + a: [[f32x3_align4; 4]; 2], + } + print_mismatch::(); } } From 4480b936a36f1bb07a862b1709f4b396d615c9d8 Mon Sep 17 00:00:00 2001 From: chronicl Date: Thu, 24 Jul 2025 00:54:59 +0200 Subject: [PATCH 145/182] TypeLayoutCompatibleWith tests started --- .../rust_types/type_layout/compatible_with.rs | 92 +++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 869c6ab..8a41fec 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -22,8 +22,8 @@ use super::{recipe::TypeLayoutRecipe, Repr}; /// [`WgslStorage`] and [`WgslUniform`]. /// /// To be "useable" or "compatible with" an address space means that the type layout -/// - satisfies the layout requirements of the address space /// - is representable in the target language +/// - satisfies the layout requirements of the address space /// /// Wgsl has only one representation of types - there is no choice between std140 and std430 /// like in glsl - so to be representable in wgsl means that the type layout produced by @@ -88,7 +88,7 @@ impl TypeLayoutCompatibleWith { if layout != layout_unified { match try_find_mismatch(&layout, &layout_unified) { Some(mismatch) => { - return Err(AddressSpaceError::DoesntSatisfyRequirements(LayoutError { + return Err(AddressSpaceError::RequirementsNotSatisfied(LayoutError { recipe, address_space, mismatch, @@ -122,7 +122,7 @@ impl AddressSpace for WgslUniform { const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslUniform; } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum AddressSpaceEnum { WgslStorage, WgslUniform, @@ -149,7 +149,7 @@ pub enum AddressSpaceError { #[error("{} is not representable in {}:\n{0}", .0.recipe, .0.address_space.language())] NotRepresentable(LayoutError), #[error("Address space requirements not satisfied:\n{0}")] - DoesntSatisfyRequirements(LayoutError), + RequirementsNotSatisfied(LayoutError), #[error("Unknown layout error occured for {0} in {1}.")] UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), #[error( @@ -278,10 +278,10 @@ impl std::fmt::Display for LayoutError { "Field `{}` in `{}` needs to be {} byte aligned in {}, but has a byte-offset of {}, which is only {} byte aligned", field_name, struct_left.name, expected_align, self.address_space, offset, actual_align )?; - writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; + writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; write_struct(f, struct_left, Some(*field_index), self.colored)?; - writeln!(f, "Potential solutions include:")?; + writeln!(f, "\nPotential solutions include:")?; writeln!( f, @@ -328,7 +328,7 @@ impl std::fmt::Display for LayoutError { }; } } - todo!() + Ok(()) } } @@ -359,3 +359,81 @@ where Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::pipeline_kind::Render; + use crate::{self as shame, EncodingGuard, ThreadIsAlreadyEncoding}; + use shame as sm; + use shame::{aliases::*, GpuLayout}; + + const PRINT: bool = true; + + macro_rules! is_struct_mismatch { + ($result:expr, $as_error:ident, $mismatch:pat) => { + { + if let Err(e) = &$result && PRINT { + println!("{e}"); + } + matches!( + $result, + Err(AddressSpaceError::$as_error(LayoutError { + mismatch: LayoutMismatch::Struct { + mismatch: $mismatch, + .. + }, + .. + })) + ) + } + }; + } + + fn enable_color() -> Result, ThreadIsAlreadyEncoding> { + sm::start_encoding(sm::Settings::default()) + } + + #[test] + fn test_field_offset_error_not_representable() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + #[gpu_repr(packed)] + struct A { + a: f32x1, + // has offset 4, but in wgsl's storage/uniform address space, it needs to be 16 byte aligned + b: f32x3, + } + + // The error variant is NotRepresentable, because there is no way to represent it in wgsl, + // because an offset of 4 is not possible for f32x3, because it is 16 byte aligned. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(A::layout_recipe()), + NotRepresentable, + StructMismatch::FieldOffset { .. } + )); + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(A::layout_recipe()), + NotRepresentable, + StructMismatch::FieldOffset { .. } + )); + + #[derive(sm::GpuLayout)] + #[gpu_repr(wgsl)] + struct B { + a: f32x1, + // offset 4, but wgsl's uniform address space requires 16 byte alignment + b: sm::Array, + } + + // The error variant is RequirementsNotSatisfied, because B is representable in wgsl, + // because it's Repr::Wgsl, but it does not satisfy the requirements of wgsl's uniform address space, + // because the array has an align of 4 in Repr::Wgsl, but an align of 16 in Repr::WgslUniform. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(B::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldOffset { .. } + )); + } +} From 446079971dd2709278293f83bb41ee7dbdb6ce48 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 26 Jul 2025 18:37:36 +0200 Subject: [PATCH 146/182] TypeLayoutCompatibleWith done --- shame/src/backend/language.rs | 8 + .../src/frontend/rust_types/layout_traits.rs | 4 +- shame/src/frontend/rust_types/mem.rs | 4 +- shame/src/frontend/rust_types/packed_vec.rs | 4 +- .../rust_types/type_layout/compatible_with.rs | 750 ++++++++++++------ .../rust_types/type_layout/display.rs | 45 +- .../src/frontend/rust_types/type_layout/eq.rs | 295 ++++--- .../frontend/rust_types/type_layout/mod.rs | 12 +- .../type_layout/recipe/align_size.rs | 19 +- .../type_layout/recipe/to_layout.rs | 114 ++- shame/src/ir/pipeline/wip_pipeline.rs | 4 +- shame_derive/src/derive_layout.rs | 8 - 12 files changed, 876 insertions(+), 391 deletions(-) diff --git a/shame/src/backend/language.rs b/shame/src/backend/language.rs index 3b4e4bd..83fc396 100644 --- a/shame/src/backend/language.rs +++ b/shame/src/backend/language.rs @@ -14,3 +14,11 @@ pub enum Language { Wgsl, // SpirV } + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Language::Wgsl => write!(f, "wgsl"), + } + } +} diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 1e5643c..4558781 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -24,7 +24,7 @@ use super::mem::AddressSpace; use super::reference::{AccessMode, AccessModeReadable}; use super::struct_::{BufferFields, SizedFields, Struct}; use super::type_layout::recipe::{self, array_stride, Vector, ScalarType}; -use super::type_layout::{self, FieldLayout, StructLayout, TypeLayout, DEFAULT_REPR}; +use super::type_layout::{self, FieldLayout, StructLayout, TypeLayout}; use super::type_traits::{ BindingArgs, GpuAligned, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools, NoHandles, VertexAttribute, }; @@ -246,7 +246,7 @@ pub(crate) fn check_layout_push_error( (Some(cpu_size), Some(gpu_size)) => { let cpu_stride = cpu_size; // - let gpu_stride = array_stride(gpu_layout.align(), gpu_size, DEFAULT_REPR); + let gpu_stride = array_stride(gpu_layout.align(), gpu_size, Repr::default()); if cpu_stride != gpu_stride { Err(LayoutError::StrideMismatch { diff --git a/shame/src/frontend/rust_types/mem.rs b/shame/src/frontend/rust_types/mem.rs index 2050947..f9fb2f6 100644 --- a/shame/src/frontend/rust_types/mem.rs +++ b/shame/src/frontend/rust_types/mem.rs @@ -57,12 +57,12 @@ pub struct PushConstant(()); /// the only source of uniform values, and reading in it does not necessarily /// produce uniform values (i.e. during array lookup, if the array index is /// not uniform). -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct Uniform(()); /// ### the address space of storage buffer bindings /// /// readable and writeable, visible across all threads of a dispatch/drawcall -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct Storage(()); /// ### the address space of texture-/sampler bindings /// diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 537419d..6a358dc 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -22,7 +22,7 @@ use super::{ layout_traits::{from_single_any, ArrayElementsUnsizedError, FromAnys, GpuLayout}, len::LenEven, scalar_type::ScalarType, - type_layout::{self, recipe, Repr, TypeLayout, DEFAULT_REPR}, + type_layout::{self, recipe, Repr, TypeLayout}, type_traits::{GpuAligned, GpuSized, NoAtomics, NoBools, NoHandles, VertexAttribute}, vec::IsVec, GpuType, @@ -144,7 +144,7 @@ impl GpuLayout for PackedVec { fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { let sized_ty: recipe::SizedType = Self::layout_recipe_sized(); let name = sized_ty.to_string().into(); - let layout = sized_ty.layout(DEFAULT_REPR); + let layout = sized_ty.layout(Repr::default()); Some(Ok((name, layout))) } } diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 8a41fec..50b9f33 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -7,80 +7,115 @@ use crate::{ frontend::rust_types::type_layout::{ display::LayoutInfo, eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, + recipe::to_layout::RecipeContains, ArrayLayout, }, ir::{ir_type::max_u64_po2_dividing, recording::Context}, - TypeLayout, + mem, Language, TypeLayout, }; use super::{recipe::TypeLayoutRecipe, Repr}; -/// `TypeLayoutCompatibleWith` is a `TypeLayoutRecipe` with the additional -/// guarantee that the resulting `TypeLayout` is useable in the specified `AddressSpace`. +pub use mem::{Storage, Uniform}; + +/// `TypeLayoutCompatibleWith` is a [`TypeLayoutRecipe`] with the additional +/// guarantee that the [`TypeLayout`] it produces is compatible with the specified `AddressSpace`. +/// +/// Address space requirements are language specific, which is why `TypeLayoutCompatibleWith` constructors +/// additionally take a [`Language`] parameter. /// -/// The address spaces are language specific. For example, in WGSL there are two address spaces: -/// [`WgslStorage`] and [`WgslUniform`]. +/// To be "compatible with" an address space means that +/// - the recipe is **valid** ([`TypeLayoutRecipe::layout`] succeeds) +/// - the type layout **satisfies the layout requirements** of the address space +/// - the type layout recipe is **representable** in the target language /// -/// To be "useable" or "compatible with" an address space means that the type layout -/// - is representable in the target language -/// - satisfies the layout requirements of the address space +/// To be representable in a language means that the type layout recipe, can be expressed in the +/// language's type system: +/// 1. all types in the recipe can be expressed in the target language (for example `bool` or `PackedVector` can't be expressed in wgsl) +/// 2. the available layout algorithms in the target language can produce the same layout as the one produced by the recipe +/// 3. support for the custom attributes the recipe uses, such as `#[align(N)]` and `#[size(N)]`. +/// Custom attributes may be rejected by the target language itself (NotRepresentable error) +/// or by the layout algorithms specified in the recipe (InvalidRecipe error during `TypeLayoutRecipe -> TypeLayout` conversion). /// -/// Wgsl has only one representation of types - there is no choice between std140 and std430 -/// like in glsl - so to be representable in wgsl means that the type layout produced by -/// the recipe is the same as the one produced by the same recipe but with all structs -/// in the recipe using the Repr::Wgsl layout algorithm. Aditionally, all custom attributes used -/// by the recipe need to be support by wgsl, which are only the struct field attributes -/// `#[align(N)]` and `#[size(N)]` currently. +/// For example for wgsl we have +/// 1. PackedVector can be part of a recipe, but can not be expressed in wgsl, +/// so a recipe containing a PackedVector is not representable in wgsl. +/// 2. Wgsl has only one layout algorithm (`Repr::Wgsl`) - there is no choice between std140 and std430 +/// like in glsl - so to be representable in wgsl the type layout produced by the recipe +/// has to be the same as the one produced by the same recipe but using exclusively the Repr::Wgsl +/// layout algorithm instead of the layout algorithms specified in the recipe. +/// 3. Wgsl only supports custom struct field attributes `#[align(N)]` and `#[size(N)]` currently. +#[derive(Debug, Clone)] pub struct TypeLayoutCompatibleWith { recipe: TypeLayoutRecipe, _phantom: std::marker::PhantomData, } impl TypeLayoutCompatibleWith { - pub fn try_from(recipe: TypeLayoutRecipe) -> Result { + pub fn try_from(language: Language, recipe: TypeLayoutRecipe) -> Result { let address_space = AS::ADDRESS_SPACE; let layout = recipe.layout(); - match (address_space, layout.byte_size()) { + match (language, address_space, layout.byte_size()) { // Must be sized in wgsl's uniform address space - (AddressSpaceEnum::WgslUniform, None) => return Err(AddressSpaceError::MustBeSized(recipe, address_space)), - (AddressSpaceEnum::WgslUniform, Some(_)) | (AddressSpaceEnum::WgslStorage, _) => {} + (Language::Wgsl, AddressSpaceEnum::Uniform, None) => { + return Err(RequirementsNotSatisfied::MustBeSized(recipe, language, address_space).into()); + } + (Language::Wgsl, AddressSpaceEnum::Uniform, Some(_)) | (Language::Wgsl, AddressSpaceEnum::Storage, _) => {} } - // Check that the type layout is representable in the target language - match address_space { - AddressSpaceEnum::WgslStorage | AddressSpaceEnum::WgslUniform => { - // Wgsl has only one type representation: Repr::Wgsl, so the layout produced by the recipe - // is representable in wgsl iff the layout produced by the same recipe but with - // all structs in the recipe using Repr::Wgsl is the same and all custom attributes - // used by the recipe are supported by wgsl, which is checked in `TypeLayoutRecipe::layout` - // TODO(chronicl) line above - let recipe_unified = recipe.to_unified_repr(Repr::Wgsl); - let layout_unified = recipe_unified.layout(); - if layout != layout_unified { - match try_find_mismatch(&layout, &layout_unified) { + // Check that the recipe is representable in the target language. + // See `TypeLayoutCompatibleWith` docs for more details on what it means to be representable. + match (language, address_space) { + (Language::Wgsl, AddressSpaceEnum::Storage | AddressSpaceEnum::Uniform) => { + // We match like this, so that future additions to `RecipeContains` lead us here. + match RecipeContains::CustomFieldAlign { + // supported in wgsl + RecipeContains::CustomFieldAlign | RecipeContains::CustomFieldSize | + // not supported in wgsl + RecipeContains::PackedVector => { + if recipe.contains(RecipeContains::PackedVector) { + return Err(NotRepresentable::MayNotContain( + recipe, + language, + address_space, + RecipeContains::PackedVector, + ) + .into()); + } + } + } + + // Wgsl has only one layout algorithm + let recipe_wgsl = recipe.to_unified_repr(Repr::Wgsl); + let layout_wgsl = recipe_wgsl.layout_with_default_repr(Repr::Wgsl); + if layout != layout_wgsl { + match try_find_mismatch(&layout, &layout_wgsl) { Some(mismatch) => { - return Err(AddressSpaceError::NotRepresentable(LayoutError { + return Err(NotRepresentable::LayoutError(LayoutError { recipe, + kind: LayoutErrorKind::NotRepresentable, + language, address_space, mismatch, colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) .unwrap_or(false), - })); + }) + .into()); } - None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), + None => return Err(NotRepresentable::UnknownLayoutError(recipe, address_space).into()), } } } } // Check that the type layout satisfies the requirements of the address space - match address_space { - AddressSpaceEnum::WgslStorage => { + match (language, address_space) { + (Language::Wgsl, AddressSpaceEnum::Storage) => { // As long as the recipe is representable in wgsl, it satifies the storage address space requirements. // We already checked that the recipe is representable in wgsl above. } - AddressSpaceEnum::WgslUniform => { + (Language::Wgsl, AddressSpaceEnum::Uniform) => { // Repr::WgslUniform is made for exactly this purpose: to check that the type layout // satisfies the requirements of wgsl's uniform address space. let recipe_unified = recipe.to_unified_repr(Repr::WgslUniform); @@ -88,21 +123,23 @@ impl TypeLayoutCompatibleWith { if layout != layout_unified { match try_find_mismatch(&layout, &layout_unified) { Some(mismatch) => { - return Err(AddressSpaceError::RequirementsNotSatisfied(LayoutError { + return Err(RequirementsNotSatisfied::LayoutError(LayoutError { recipe, + kind: LayoutErrorKind::RequirementsNotSatisfied, + language, address_space, mismatch, colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) .unwrap_or(false), - })); + }) + .into()); } - None => return Err(AddressSpaceError::UnknownLayoutError(recipe, address_space)), + None => return Err(RequirementsNotSatisfied::UnknownLayoutError(recipe, address_space).into()), } } } } - Ok(Self { recipe, _phantom: std::marker::PhantomData, @@ -113,232 +150,366 @@ impl TypeLayoutCompatibleWith { pub trait AddressSpace { const ADDRESS_SPACE: AddressSpaceEnum; } -pub struct WgslStorage; -pub struct WgslUniform; -impl AddressSpace for WgslStorage { - const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslStorage; +impl AddressSpace for Storage { + const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::Storage; } -impl AddressSpace for WgslUniform { - const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::WgslUniform; +impl AddressSpace for Uniform { + const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::Uniform; } - #[derive(Debug, Clone, Copy, PartialEq)] pub enum AddressSpaceEnum { - WgslStorage, - WgslUniform, + Storage, + Uniform, } impl std::fmt::Display for AddressSpaceEnum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AddressSpaceEnum::WgslStorage => f.write_str("wgsl's storage address space"), - AddressSpaceEnum::WgslUniform => f.write_str("wgsl's uniform address space"), - } - } -} -impl AddressSpaceEnum { - pub fn language(&self) -> &'static str { - match self { - AddressSpaceEnum::WgslStorage => "wgsl", - AddressSpaceEnum::WgslUniform => "wgsl", + AddressSpaceEnum::Storage => write!(f, "storage address space"), + AddressSpaceEnum::Uniform => write!(f, "uniform address space"), } } } #[derive(thiserror::Error, Debug, Clone)] pub enum AddressSpaceError { - #[error("{} is not representable in {}:\n{0}", .0.recipe, .0.address_space.language())] - NotRepresentable(LayoutError), - #[error("Address space requirements not satisfied:\n{0}")] - RequirementsNotSatisfied(LayoutError), + #[error("{0}")] + NotRepresentable(#[from] NotRepresentable), + #[error("{0}")] + RequirementsNotSatisfied(#[from] RequirementsNotSatisfied), +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum NotRepresentable { + #[error("{0}")] + LayoutError(LayoutError), + #[error("{0} contains a {3}, which is not allowed in {1}'s {2}.")] + MayNotContain(TypeLayoutRecipe, Language, AddressSpaceEnum, RecipeContains), #[error("Unknown layout error occured for {0} in {1}.")] UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum RequirementsNotSatisfied { + #[error("{0}")] + LayoutError(LayoutError), #[error( - "The size of `{0}` on the gpu is not known at compile time. {1} \ + "The size of `{0}` on the gpu is not known at compile time. {1}'s {2} \ requires that the size of {0} on the gpu is known at compile time." )] - MustBeSized(TypeLayoutRecipe, AddressSpaceEnum), - #[error("{0} contains a `PackedVector`, which are not allowed in {1}.")] - MayNotContainPackedVec(TypeLayoutRecipe, AddressSpaceEnum), + MustBeSized(TypeLayoutRecipe, Language, AddressSpaceEnum), + #[error("Unknown layout error occured for {0} in {1}.")] + UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), } #[derive(Debug, Clone)] pub struct LayoutError { recipe: TypeLayoutRecipe, - address_space: AddressSpaceEnum, mismatch: LayoutMismatch, + + /// Used to adjust the error message + /// to fit `NotRepresentable` or `RequirementsNotSatisfied`. + kind: LayoutErrorKind, + language: Language, + address_space: AddressSpaceEnum, + colored: bool, } +#[derive(Debug, Clone, Copy)] +pub enum LayoutErrorKind { + NotRepresentable, + RequirementsNotSatisfied, +} + +impl LayoutError { + /// Returns the context the error occurred in. The "{language}" in case of a `NotRepresentable` error, + /// or the "{language}'s {address_space}" in case of a `RequirementsNotSatisfied` error. + fn context(&self) -> &'static str { + match self.kind { + LayoutErrorKind::NotRepresentable => match self.language { + Language::Wgsl => "wgsl", + }, + LayoutErrorKind::RequirementsNotSatisfied => match (self.language, self.address_space) { + (Language::Wgsl, AddressSpaceEnum::Storage) => "wgsl's storage address space", + (Language::Wgsl, AddressSpaceEnum::Uniform) => "wgsl's uniform address space", + }, + } + } +} + impl std::error::Error for LayoutError {} impl std::fmt::Display for LayoutError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let types_are_the_same = "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same"; + match self.kind { + LayoutErrorKind::NotRepresentable => { + writeln!(f, "`{}` is not representable in {}:", self.recipe, self.language)?; + } + LayoutErrorKind::RequirementsNotSatisfied => { + writeln!( + f, + "`{}` does not satisfy the layout requirements of {}:", + self.recipe, + self.context() + )?; + } + } + match &self.mismatch { LayoutMismatch::TopLevel { layout_left, layout_right, mismatch, - } => match mismatch { - TopLevelMismatch::Type => unreachable!("{}", types_are_the_same), - TopLevelMismatch::ArrayStride { - array_left, - array_right, - } => { - if array_left.short_name() == array_right.short_name() { - writeln!( - f, - "`{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - array_right.byte_stride, - self.address_space, - array_left.byte_stride - )?; - } else { - writeln!( - f, - "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - layout_left.short_name(), - array_right.byte_stride, - self.address_space, - array_left.byte_stride - )?; - } - } - // TODO(chronicl) fix byte size message for when the byte size mismatch is happening - // in a nested array - TopLevelMismatch::ByteSize { .. } => { - writeln!( - f, - "`{}` has a byte size of {} in {}, but has a byte size of {}.", - layout_left.short_name(), - UnwrapOrStr(layout_left.byte_size(), ""), - self.address_space, - UnwrapOrStr(layout_right.byte_size(), "") - )?; - } - }, + } => write_top_level_mismatch(f, self, layout_left, layout_right, mismatch), LayoutMismatch::Struct { struct_left, struct_right, mismatch, - } => { - match mismatch { - StructMismatch::FieldLayout { - field_index, - mismatch: - TopLevelMismatch::ArrayStride { - array_left, - array_right, - }, - } => { - writeln!( - f, - "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - struct_left.short_name(), - array_right.byte_stride, - self.address_space, - array_left.byte_stride - )?; - writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; - write_struct(f, struct_left, Some(*field_index), self.colored)?; - } - StructMismatch::FieldLayout { - field_index, - mismatch: TopLevelMismatch::ByteSize { left, right }, - } => { - let field_left = &struct_left.fields[*field_index]; - let field_right = &struct_right.fields[*field_index]; - - // TODO(chronicl) fix byte size message for when the byte size mismatch is happening - // in a nested array - writeln!( - f, - "Field `{}` in `{}` requires a byte size of {} in {}, but has a byte size of {}", - field_left.name, - struct_left.name, - UnwrapOrStr(field_right.ty.byte_size(), ""), - self.address_space, - UnwrapOrStr(field_left.ty.byte_size(), "") - )?; - writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; - write_struct(f, struct_left, Some(*field_index), self.colored)?; - } - StructMismatch::FieldOffset { field_index } => { - let field_left = &struct_left.fields[*field_index]; - let field_right = &struct_right.fields[*field_index]; - let field_name = &field_left.name; - let offset = field_left.rel_byte_offset; - let expected_align = field_right.ty.align().as_u64(); - let actual_align = max_u64_po2_dividing(field_left.rel_byte_offset); - - writeln!( - f, - "Field `{}` in `{}` needs to be {} byte aligned in {}, but has a byte-offset of {}, which is only {} byte aligned", - field_name, struct_left.name, expected_align, self.address_space, offset, actual_align - )?; - writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; - write_struct(f, struct_left, Some(*field_index), self.colored)?; + } => write_struct_mismatch(f, self, struct_left, struct_right, mismatch), + } + } +} - writeln!(f, "\nPotential solutions include:")?; +fn write_top_level_mismatch( + f: &mut std::fmt::Formatter<'_>, + error: &LayoutError, + layout_left: &TypeLayout, + layout_right: &TypeLayout, + mismatch: &TopLevelMismatch, +) -> Result<(), std::fmt::Error> { + match mismatch { + TopLevelMismatch::Type => unreachable!( + "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same" + ), + TopLevelMismatch::ArrayStride { + array_left, + array_right, + } => { + let outer_most_array_has_mismatch = array_left.short_name() == layout_left.short_name(); + if outer_most_array_has_mismatch { + writeln!( + f, + "`{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + array_right.byte_stride, + error.context(), + array_left.byte_stride + )?; + } else { + writeln!( + f, + "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + layout_left.short_name(), + array_right.byte_stride, + error.context(), + array_left.byte_stride + )?; + } + } + TopLevelMismatch::ByteSize { left, right } => { + let outer_most_array_has_mismatch = left.short_name() == layout_left.short_name(); + if outer_most_array_has_mismatch { + writeln!( + f, + "`{}` has a byte size of {} in {}, but has a byte size of {}.", + layout_left.short_name(), + UnwrapOrStr(layout_left.byte_size(), ""), + error.context(), + UnwrapOrStr(layout_right.byte_size(), "") + )?; + } else { + writeln!( + f, + "`{}` in `{}` has a byte size of {} in {}, but has a byte size of {}.", + layout_left.short_name(), + layout_left.short_name(), + UnwrapOrStr(layout_left.byte_size(), ""), + error.context(), + UnwrapOrStr(layout_right.byte_size(), "") + )?; + } + } + } + Ok(()) +} - writeln!( +fn write_struct_mismatch( + f: &mut std::fmt::Formatter<'_>, + error: &LayoutError, + struct_left: &StructLayout, + struct_right: &StructLayout, + mismatch: &StructMismatch, +) -> Result<(), std::fmt::Error> { + match mismatch { + StructMismatch::FieldLayout { + field_index, + field_left, + mismatch: + TopLevelMismatch::ArrayStride { + array_left, + array_right, + }, + .. + } => { + let outer_most_array_has_mismatch = field_left.ty.short_name() == array_left.short_name(); + let layout_info = if outer_most_array_has_mismatch { + LayoutInfo::STRIDE + } else { + // if an inner array has the stride mismatch, showing the outer array's stride could be confusing + LayoutInfo::NONE + }; + + writeln!( + f, + "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + struct_left.short_name(), + array_right.byte_stride, + error.context(), + array_left.byte_stride + )?; + writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; + write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; + } + StructMismatch::FieldLayout { + field_index, + field_left, + field_right, + mismatch: TopLevelMismatch::ByteSize { left, right }, + } => { + let outer_most_array_has_mismatch = field_left.ty.short_name() == left.short_name(); + let layout_info = if outer_most_array_has_mismatch { + LayoutInfo::SIZE + } else { + // if an inner array has the byte size mismatch, showing the outer array's byte size could be confusing + LayoutInfo::NONE + }; + + if !outer_most_array_has_mismatch { + write!(f, "`{}` in field", left.short_name())?; + } else { + write!(f, "Field")?; + } + writeln!( + f, + " `{}` of `{}` requires a byte size of {} in {}, but has a byte size of {}", + field_left.name, + struct_left.name, + UnwrapOrStr(field_right.ty.byte_size(), ""), + error.context(), + UnwrapOrStr(field_left.ty.byte_size(), "") + )?; + writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; + write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; + } + StructMismatch::FieldOffset { + field_index, + field_left, + field_right, + } => { + let field_name = &field_left.name; + let offset = field_left.rel_byte_offset; + let expected_align = field_right.ty.align().as_u64(); + let actual_align = max_u64_po2_dividing(field_left.rel_byte_offset); + + writeln!( + f, + "Field `{}` of `{}` needs to be {} byte aligned in {}, but has a byte-offset of {}, which is only {} byte aligned", + field_name, + struct_left.name, + expected_align, + error.context(), + offset, + actual_align + )?; + + writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; + write_struct( + f, + struct_left, + LayoutInfo::OFFSET | LayoutInfo::ALIGN | LayoutInfo::SIZE, + Some(*field_index), + error.colored, + )?; + + writeln!(f, "\nPotential solutions include:")?; + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}`", + field_right.ty.align().as_u32(), + field_name + )?; + writeln!( + f, + "- increase the offset of `{field_name}` until it is divisible by {expected_align} by making previous fields larger or adding fields before it" + )?; + match (error.kind, error.language, error.address_space) { + (LayoutErrorKind::RequirementsNotSatisfied, Language::Wgsl, AddressSpaceEnum::Uniform) => { + writeln!(f, "- use a storage binding instead of a uniform binding")?; + } + ( + LayoutErrorKind::NotRepresentable | LayoutErrorKind::RequirementsNotSatisfied, + Language::Wgsl, + AddressSpaceEnum::Storage | AddressSpaceEnum::Uniform, + ) => {} + } + writeln!(f)?; + + match error.language { + Language::Wgsl => { + match error.address_space { + AddressSpaceEnum::Uniform => writeln!( f, - "- add an #[align({})] attribute to the definition of `{}`", - field_right.ty.align().as_u32(), - field_name - )?; - writeln!( + "In {}, structs, arrays and array elements must be at least 16 byte aligned.", + error.context() + )?, + AddressSpaceEnum::Storage => {} + } + + match (error.kind, error.address_space) { + ( + LayoutErrorKind::RequirementsNotSatisfied, + AddressSpaceEnum::Uniform | AddressSpaceEnum::Storage, + ) => writeln!( f, - "- increase the offset of `{field_name}` until it is divisible by {expected_align} by making previous fields larger or adding fields before it" - )?; - writeln!( + "More info about the wgsl's {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + error.address_space + )?, + (LayoutErrorKind::NotRepresentable, _) => writeln!( f, - "- if you are using the uniform address space, use the storage address space instead" - )?; - writeln!(f)?; - - - match self.address_space { - AddressSpaceEnum::WgslUniform => writeln!( - f, - "In the {}, structs, arrays and array elements must be at least 16 byte aligned.", - self.address_space - )?, - AddressSpaceEnum::WgslStorage => {} - } - - match self.address_space { - AddressSpaceEnum::WgslUniform | AddressSpaceEnum::WgslStorage => writeln!( - f, - "More info about the {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - self.address_space - )?, - } - } - StructMismatch::FieldCount | - StructMismatch::FieldName { .. } | - StructMismatch::FieldLayout { - mismatch: TopLevelMismatch::Type, - .. - } => { - unreachable!("{}", types_are_the_same) + "More info about the wgsl's layout algorithm can be found at https://www.w3.org/TR/WGSL/#alignment-and-size" + )?, } - }; + } } } - Ok(()) + StructMismatch::FieldCount | + StructMismatch::FieldName { .. } | + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::Type, + .. + } => { + unreachable!( + "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same" + ) + } } + Ok(()) } -fn write_struct(f: &mut W, s: &StructLayout, highlight_field: Option, colored: bool) -> std::fmt::Result +fn write_struct( + f: &mut W, + s: &StructLayout, + layout_info: LayoutInfo, + highlight_field: Option, + colored: bool, +) -> std::fmt::Result where W: Write, { let use_256_color_mode = false; - let mut writer = s.writer(LayoutInfo::ALL); + let mut writer = s.writer(layout_info); writer.writeln_header(f); writer.writeln_struct_declaration(f); for field_index in 0..s.fields.len() { @@ -378,24 +549,24 @@ mod tests { } matches!( $result, - Err(AddressSpaceError::$as_error(LayoutError { + Err(AddressSpaceError::$as_error($as_error::LayoutError(LayoutError { mismatch: LayoutMismatch::Struct { mismatch: $mismatch, .. }, .. - })) + }))) ) } }; } - fn enable_color() -> Result, ThreadIsAlreadyEncoding> { - sm::start_encoding(sm::Settings::default()) + fn enable_color() -> Option, ThreadIsAlreadyEncoding>> { + PRINT.then(|| sm::start_encoding(sm::Settings::default())) } #[test] - fn test_field_offset_error_not_representable() { + fn field_offset_error_not_representable() { let _guard = enable_color(); #[derive(sm::GpuLayout)] @@ -407,33 +578,162 @@ mod tests { } // The error variant is NotRepresentable, because there is no way to represent it in wgsl, - // because an offset of 4 is not possible for f32x3, because it is 16 byte aligned. + // because an offset of 4 is not possible for f32x3, because needs to be 16 byte aligned. assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(A::layout_recipe()), + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), NotRepresentable, StructMismatch::FieldOffset { .. } )); assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(A::layout_recipe()), + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), NotRepresentable, StructMismatch::FieldOffset { .. } )); + } + + #[test] + fn wgsl_uniform_array_stride_requirements_not_satisfied() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + struct A { + a: f32x1, + // has stride 4, but wgsl's uniform address space requires a stride of 16. + // also, has wrong offset, because array align is multiple of 16 in wgsl's uniform address space. + // array stride error has higher priority than field offset error, + b: sm::Array>, + } + + // The error variant is RequirementsNotSatisfied, because the array has a stride of 4 in Repr::Packed, + // but wgsl's uniform address space requires a stride of 16. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::ArrayStride { .. }, + .. + } + )); + // Testing that the error remains the same when nested in another struct #[derive(sm::GpuLayout)] - #[gpu_repr(wgsl)] struct B { + a: sm::Struct, + } + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, B::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::ArrayStride { .. }, + .. + } + )); + + // Testing that the error remains the same when nested in an array + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from( + Language::Wgsl, + , sm::Size<1>>>::layout_recipe() + ), + RequirementsNotSatisfied, + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::ArrayStride { .. }, + .. + } + )); + } + + #[test] + fn wgsl_uniform_field_offset_requirements_not_satisfied() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + struct A { a: f32x1, - // offset 4, but wgsl's uniform address space requires 16 byte alignment - b: sm::Array, + b: sm::Struct, } + #[derive(sm::GpuLayout)] + struct B { + a: f32x1, + } + + // The error variant is RequirementsNotSatisfied, because the array has a stride of 4 in Repr::Packed, + // but wgsl's uniform address space requires a stride of 16. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldOffset { .. } + )); + + // Testing that the error remains the same when nested in another struct + #[derive(sm::GpuLayout)] + struct C { + a: sm::Struct, + } + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, C::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldOffset { .. } + )); - // The error variant is RequirementsNotSatisfied, because B is representable in wgsl, - // because it's Repr::Wgsl, but it does not satisfy the requirements of wgsl's uniform address space, - // because the array has an align of 4 in Repr::Wgsl, but an align of 16 in Repr::WgslUniform. + // Testing that the error remains the same when nested in an array assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(B::layout_recipe()), + TypeLayoutCompatibleWith::::try_from( + Language::Wgsl, + , sm::Size<1>>>::layout_recipe() + ), RequirementsNotSatisfied, StructMismatch::FieldOffset { .. } )); } + + #[test] + fn wgsl_uniform_must_be_sized() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + struct A { + a: sm::Array, + } + + let e = TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).unwrap_err(); + if PRINT { + println!("{e}"); + } + assert!(matches!( + e, + AddressSpaceError::RequirementsNotSatisfied(RequirementsNotSatisfied::MustBeSized( + _, + Language::Wgsl, + AddressSpaceEnum::Uniform + )) + )); + + // Storage address space should allow unsized types + assert!(TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).is_ok()); + } + + #[test] + fn wgsl_storage_may_not_contain_packed_vec() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + #[gpu_repr(packed)] + struct A { + a: sm::packed::snorm16x2, + } + let e = TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).unwrap_err(); + if PRINT { + println!("{e}"); + } + assert!(matches!( + e, + AddressSpaceError::NotRepresentable(NotRepresentable::MayNotContain( + _, + Language::Wgsl, + AddressSpaceEnum::Storage, + RecipeContains::PackedVector + )) + )); + } } diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs index 0349adc..041f3d1 100644 --- a/shame/src/frontend/rust_types/type_layout/display.rs +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -67,6 +67,7 @@ impl TypeLayout { } impl StructLayout { + /// a short name for this `StructLayout`, useful for printing inline pub fn short_name(&self) -> String { self.name.to_string() } pub(crate) fn to_string_with_layout_info(&self, layout_info: LayoutInfo) -> Result { @@ -91,6 +92,7 @@ impl StructLayout { } impl ArrayLayout { + /// a short name for this `ArrayLayout`, useful for printing inline pub fn short_name(&self) -> String { match self.len { Some(n) => format!("array<{}, {n}>", self.element_ty.short_name()), @@ -180,11 +182,12 @@ impl<'a> StructWriter<'a> { Some(n) => n.min(self.s.fields.len()), None => self.s.fields.len(), }; - let layout_info_offset = (0..fields) - .map(|i| self.field_declaration(i).len()) - .max() - .unwrap_or(0) - .max(self.struct_declaration().len()); + let layout_info_offset = 1 + + (0..fields) + .map(|i| self.field_declaration(i).len()) + .max() + .unwrap_or(0) + .max(self.struct_declaration().len()); self.layout_info_offset = layout_info_offset; } @@ -199,7 +202,7 @@ impl<'a> StructWriter<'a> { fn field_declaration(&self, field_index: usize) -> String { match self.s.fields.get(field_index) { Some(field) => format!("{}{}: {},", self.tab, field.name, field.ty.short_name()), - None => String::new(), + None => format!("{}field {field_index} not found,", self.tab), } } @@ -220,18 +223,24 @@ impl<'a> StructWriter<'a> { pub(crate) fn write_field(&self, f: &mut W, field_index: usize) -> std::fmt::Result { use TypeLayout::*; - let field = &self.s.fields[field_index]; - let info = self.layout_info.format( - Some(field.rel_byte_offset), - field.ty.align(), - field.ty.byte_size(), - match &field.ty { - Array(array) => Some(array.byte_stride), - Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, - }, - ); - let info_offset = self.layout_info_offset(); - write!(f, "{:info_offset$}{info}", self.field_declaration(field_index)) + match self.s.fields.get(field_index) { + Some(field) => { + let info = self.layout_info.format( + Some(field.rel_byte_offset), + field.ty.align(), + field.ty.byte_size(), + match &field.ty { + Array(array) => Some(array.byte_stride), + Vector(_) | PackedVector(_) | Matrix(_) | Struct(_) => None, + }, + ); + let info_offset = self.layout_info_offset(); + write!(f, "{:info_offset$}{info}", self.field_declaration(field_index)) + } + None => { + write!(f, "{}field {field_index} not found", self.tab) + } + } } pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index c0ec967..f12464b 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -17,7 +17,7 @@ use super::*; /// a: struct BOther { ... } /// } /// ``` -/// mismatches, because `B` and `BOther` don't match, then the exact mismatch +/// mismatches, because `B` and `BOther` don't match, then the exact mismatch (some field mismatch) /// between `B` and `BOther` is reported and not the field mismatch of `a` in `A` and `AOther`. /// /// Nested arrays are reported in two levels: `LayoutMismatch::TopLevel` contains the @@ -28,7 +28,7 @@ use super::*; /// a `TopLevelMismatch::ArrayStride` with the layout of `Array`. /// /// A field of nested arrays in a struct is handled in the same way by -/// `LayoutMismatch::Struct` containing the field index, which let's us access the outer +/// `LayoutMismatch::Struct` containing the field index and `FieldLayout`, which let's us access the outer /// most array, and a `TopLevelMismatch`, which let's us access the inner type layout /// where the mismatch is happening. #[derive(Debug, Clone)] @@ -63,13 +63,19 @@ pub enum TopLevelMismatch { pub enum StructMismatch { FieldName { field_index: usize, + field_left: FieldLayout, + field_right: FieldLayout, }, FieldLayout { field_index: usize, + field_left: FieldLayout, + field_right: FieldLayout, mismatch: TopLevelMismatch, }, FieldOffset { field_index: usize, + field_left: FieldLayout, + field_right: FieldLayout, }, FieldCount, } @@ -108,12 +114,12 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O Some(LayoutMismatch::TopLevel { mismatch, .. }) => { return Some(make_mismatch(mismatch)); } - // Struct mismatch, so it's not a top-level mismatch anymore + // Struct mismatch, so it's not a top-level mismatch m @ Some(LayoutMismatch::Struct { .. }) => return m, None => {} } - // Check array sizes match + // Check array lengths, which are a type mismatch if they differ if a1.len != a2.len { return Some(make_mismatch(TopLevelMismatch::Type)); } @@ -135,7 +141,8 @@ pub(crate) fn try_find_mismatch(layout1: &TypeLayout, layout2: &TypeLayout) -> O } } - // Check byte size + // Check byte size. + // We do this at the end, because type mismatches should have priority over byte size mismatches. if layout1.byte_size() != layout2.byte_size() { return Some(make_mismatch(TopLevelMismatch::ByteSize { left: layout1.clone(), @@ -156,10 +163,14 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O for (field_index, (field1, field2)) in struct1.fields.iter().zip(struct2.fields.iter()).enumerate() { // Order of checks is important here. We check in order // - field name - // - field type, byte size and if the field is/contains a struct, recursively check its fields + // - field inner mismatch // - field offset if field1.name != field2.name { - return Some(make_mismatch(StructMismatch::FieldName { field_index })); + return Some(make_mismatch(StructMismatch::FieldName { + field_index, + field_left: field1.clone(), + field_right: field2.clone(), + })); } // Recursively check field types @@ -167,20 +178,31 @@ fn try_find_struct_mismatch(struct1: &StructLayout, struct2: &StructLayout) -> O match inner_mismatch { // If it's a top-level mismatch, convert it to a field mismatch LayoutMismatch::TopLevel { mismatch, .. } => { - return Some(make_mismatch(StructMismatch::FieldLayout { field_index, mismatch })); + return Some(make_mismatch(StructMismatch::FieldLayout { + field_index, + field_left: field1.clone(), + field_right: field2.clone(), + mismatch, + })); } - // Pass through nested struct mismatches + // Pass through struct mismatches struct_mismatch @ LayoutMismatch::Struct { .. } => return Some(struct_mismatch), } } // Check field offset if field1.rel_byte_offset != field2.rel_byte_offset { - return Some(make_mismatch(StructMismatch::FieldOffset { field_index })); + return Some(make_mismatch(StructMismatch::FieldOffset { + field_index, + field_left: field1.clone(), + field_right: field2.clone(), + })); } } - // Check field count + // Check field count. + // We do this at the end, because fields are checked in order and a field count mismatch + // can be viewed as a field mismatch of one field beyond the last field of the smaller struct. if struct1.fields.len() != struct2.fields.len() { return Some(make_mismatch(StructMismatch::FieldCount)); } @@ -311,92 +333,98 @@ impl CheckEqLayoutMismatch { struct_right, mismatch, } => { - let field_name = |field_index: usize| { - if let Some(name) = struct_left.fields.get(field_index).map(|f| &f.name) { - format!("field `{name}`") - } else { - // Should never happen, but just in case we fall back to this - format!("field {field_index}") - } - }; - - writeln!( - f, - "The layouts of `{}` and `{}` do not match, because the", - struct_left.name, struct_right.name - ); + let is_top_level = &TypeLayout::Struct(Rc::new(struct_left.clone())) == a; + if is_top_level { + writeln!( + f, + "The layouts of `{}` and `{}` do not match, because the", + struct_left.name, struct_right.name + )?; + } else { + writeln!( + f, + "The layouts of `{}` and `{}`, contained in `{}` and `{}` respectively, do not match, because the", + struct_left.name, + struct_right.name, + a.short_name(), + b.short_name() + )?; + } let (mismatch_field_index, layout_info) = match &mismatch { - StructMismatch::FieldName { field_index } => { + StructMismatch::FieldName { field_index, .. } => { writeln!(f, "names of field {field_index} are different.")?; (Some(field_index), LayoutInfo::NONE) } StructMismatch::FieldLayout { field_index, + field_left, mismatch: TopLevelMismatch::Type, + .. } => { - writeln!(f, "type of {} is different.", field_name(*field_index))?; + writeln!(f, "type of {} is different.", field_left.name)?; (Some(field_index), LayoutInfo::NONE) } - // TODO(chronicl) fix byte size message for when the byte size mismatch is happening - // in a nested array StructMismatch::FieldLayout { field_index, + field_left, mismatch: TopLevelMismatch::ByteSize { left, right }, + .. } => { - match struct_left.fields.get(*field_index) { - // Inner type in (nested) array has mismatching byte size - Some(field) if &field.ty != left => { - writeln!( - f, - "byte size of `{}` is {} in `{}` and {} in `{}`.", - left.short_name(), - UnwrapOrStr(left.byte_size(), "runtime-sized"), - struct_left.name, - UnwrapOrStr(right.byte_size(), "runtime-sized"), - struct_right.name, - )?; - // Not showing byte size info, because it can be misleading since - // the inner type is the one that has mismatching byte size. - (Some(field_index), LayoutInfo::NONE) - } - _ => { - writeln!(f, "byte size of {} is different.", field_name(*field_index))?; - (Some(field_index), LayoutInfo::SIZE) - } + // Inner type in (nested) array has mismatching byte size + if &field_left.ty != left { + writeln!( + f, + "byte size of `{}` is {} in `{}` and the byte size of `{}` is {} in `{}`.", + left.short_name(), + UnwrapOrStr(left.byte_size(), "runtime-sized"), + struct_left.name, + right.short_name(), + UnwrapOrStr(right.byte_size(), "runtime-sized"), + struct_right.name, + )?; + // Not showing byte size info, because it can be misleading since + // the inner type is the one that has mismatching byte size. + (Some(field_index), LayoutInfo::NONE) + } else { + writeln!(f, "byte size of {} is different.", field_left.name)?; + (Some(field_index), LayoutInfo::SIZE) } } StructMismatch::FieldLayout { field_index, + field_left, mismatch: TopLevelMismatch::ArrayStride { array_left, array_right, }, + .. } => { - match struct_left.fields.get(*field_index) { - // Inner type in (nested) array has mismatching stride - Some(field) if field.ty.short_name() != array_left.short_name() => { - writeln!( - f, - "stride of `{}` is {} in `{}` and {} in `{}`.", - array_left.short_name(), - array_left.byte_stride, - struct_left.name, - array_right.byte_stride, - struct_right.name, - )?; - // Not showing stride info, because it can be misleading since - // the inner type is the one that has mismatching stride. - (Some(field_index), LayoutInfo::NONE) - } - _ => { - writeln!(f, "array stride of {} is different.", field_name(*field_index))?; - (Some(field_index), LayoutInfo::STRIDE) - } + // Inner type in (nested) array has mismatching stride + if field_left.ty.short_name() != array_left.short_name() { + writeln!( + f, + "stride of `{}` is {} in `{}` and {} in `{}`.", + array_left.short_name(), + array_left.byte_stride, + struct_left.name, + array_right.byte_stride, + struct_right.name, + )?; + // Not showing stride info, because it can be misleading since + // the inner type is the one that has mismatching stride. + (Some(field_index), LayoutInfo::NONE) + } else { + writeln!(f, "array stride of {} is different.", field_left.name)?; + (Some(field_index), LayoutInfo::STRIDE) } } - StructMismatch::FieldOffset { field_index } => { - writeln!(f, "offset of {} is different.", field_name(*field_index))?; + StructMismatch::FieldOffset { + field_index, + field_left, + .. + } => { + writeln!(f, "offset of {} is different.", field_left.name)?; ( Some(field_index), LayoutInfo::OFFSET | LayoutInfo::ALIGN | LayoutInfo::SIZE, @@ -454,9 +482,9 @@ impl CheckEqLayoutMismatch { } match mismatch { - StructMismatch::FieldName { field_index } | + StructMismatch::FieldName { field_index, .. } | StructMismatch::FieldLayout { field_index, .. } | - StructMismatch::FieldOffset { field_index } => { + StructMismatch::FieldOffset { field_index, .. } => { // Write mismatching field enable_color(f, hex_left)?; writer_left.write_field(f, field_index)?; @@ -515,13 +543,17 @@ where #[cfg(test)] mod tests { - use crate as shame; + use crate::pipeline_kind::Render; + use crate::{self as shame, EncodingGuard, ThreadIsAlreadyEncoding}; use shame as sm; use shame::{CpuLayout, GpuLayout, gpu_layout, cpu_layout}; use crate::aliases::*; + const PRINT: bool = true; + #[derive(Clone, Copy)] #[repr(C)] + #[allow(non_camel_case_types)] struct f32x3_align4(pub [f32; 3]); impl CpuLayout for f32x3_align4 { @@ -534,6 +566,7 @@ mod tests { #[derive(Clone, Copy)] #[repr(C)] + #[allow(non_camel_case_types)] struct f32x3_size16(pub [f32; 4]); impl CpuLayout for f32x3_size16 { @@ -544,20 +577,21 @@ mod tests { } } - fn print_mismatch() { - println!( - "{}", - super::check_eq(("gpu", &gpu_layout::()), ("cpu", &cpu_layout::())).unwrap_err() - ); + fn check_mismatch() { + let mismatch = super::check_eq(("gpu", &gpu_layout::()), ("cpu", &cpu_layout::())).unwrap_err(); + if PRINT { + println!("{mismatch}"); + } + } + + fn enable_color() -> Option, ThreadIsAlreadyEncoding>> { + PRINT.then(|| sm::start_encoding(sm::Settings::default())) } #[test] - fn test_layout_mismatch_nested() { - // For colored output - let mut encoder = sm::start_encoding(sm::Settings::default()).unwrap(); - let _ = encoder.new_render_pipeline(sm::Indexing::BufferU16); + fn test_field_name_mismatch() { + let _guard = enable_color(); - // field name mismatch #[derive(GpuLayout)] pub struct A { a: u32x1, @@ -567,10 +601,16 @@ mod tests { pub struct ACpu { b: u32, } - print_mismatch::(); + check_mismatch::(); + } - // field type mismatch - println!("The next one also shows how \"...\" is used if there are more fields after the mismatching field\n"); + #[test] + fn test_field_type_mismatch() { + let _guard = enable_color(); + + if PRINT { + println!("The error also shows how \"...\" is used if there are more fields after the mismatching field\n"); + } #[derive(GpuLayout)] pub struct B { a: f32x1, @@ -582,9 +622,13 @@ mod tests { a: u32, b: f32, } - print_mismatch::(); + check_mismatch::(); + } + + #[test] + fn test_field_offset_mismatch() { + let _guard = enable_color(); - // field offset mismatch #[derive(GpuLayout)] pub struct C { a: f32x1, @@ -596,9 +640,13 @@ mod tests { a: f32, b: f32x3_align4, } - print_mismatch::(); + check_mismatch::(); + } + + #[test] + fn test_field_byte_size_mismatch() { + let _guard = enable_color(); - // field byte size mismatch #[derive(GpuLayout)] pub struct D { a: f32x3, @@ -608,12 +656,18 @@ mod tests { pub struct DCpu { a: f32x3_size16, } - print_mismatch::(); + check_mismatch::(); + } + + #[test] + fn test_field_nested_byte_size_mismatch() { + let _guard = enable_color(); - // field nested byte size mismatch - println!( - "The next one does not show the `size` column, because it could be confusing, since the type in the array is where the mismatch happens:\n" - ); + if PRINT { + println!( + "The error does not show the `size` column, because it could be confusing, since the type in the array is where the mismatch happens:\n" + ); + } #[derive(GpuLayout)] pub struct E { a: sm::Array>, @@ -623,9 +677,13 @@ mod tests { pub struct ECpu { a: [f32x3_size16; 4], } - print_mismatch::(); + check_mismatch::(); + } + + #[test] + fn test_field_stride_mismatch() { + let _guard = enable_color(); - // field stride mismatch #[derive(GpuLayout)] pub struct F { a: sm::Array>, @@ -635,22 +693,37 @@ mod tests { pub struct FCpu { a: [f32x3_align4; 4], } - print_mismatch::(); + check_mismatch::(); + } - println!( - "The next two error messages are what is produced when non-structs, in this case arrays, are the top level types\n" - ); + #[test] + fn test_stride_mismatch() { + let _guard = enable_color(); - // stride mismatch - print_mismatch::>, [f32x3_align4; 4]>(); + if PRINT { + println!( + "The two error messages are what is produced when non-structs, in this case arrays, are the top level types\n" + ); + } + check_mismatch::>, [f32x3_align4; 4]>(); + } - // nested stride mismatch - print_mismatch::>, sm::Size<2>>, [[f32x3_align4; 4]; 2]>(); + #[test] + fn test_nested_stride_mismatch() { + let _guard = enable_color(); - // nested stride in struct mismatch - println!( - "The next one does not show the `stride` column, because it could be confusing, since the type in the array is where the mismatch happens:\n" - ); + check_mismatch::>, sm::Size<2>>, [[f32x3_align4; 4]; 2]>(); + } + + #[test] + fn test_nested_stride_in_struct_mismatch() { + let _guard = enable_color(); + + if PRINT { + println!( + "The error does not show the `stride` column, because it could be confusing, since the type in the array is where the mismatch happens:\n" + ); + } #[derive(GpuLayout)] pub struct G { a: sm::Array>, sm::Size<2>>, @@ -660,6 +733,6 @@ mod tests { pub struct GCpu { a: [[f32x3_align4; 4]; 2], } - print_mismatch::(); + check_mismatch::(); } } diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index fc0373c..3dc9380 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -1,4 +1,3 @@ -#![allow(missing_docs)] //! Everything related to type layouts. use std::{ @@ -27,8 +26,6 @@ pub(crate) mod display; pub(crate) mod eq; pub(crate) mod recipe; -pub const DEFAULT_REPR: Repr = Repr::Wgsl; - /// The memory layout of a type. /// /// This models only the layout, not other characteristics of the types. @@ -61,6 +58,7 @@ pub enum TypeLayout { Struct(Rc), } +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VectorLayout { pub byte_size: u64, @@ -71,6 +69,7 @@ pub struct VectorLayout { pub debug_is_atomic: bool, } +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PackedVectorLayout { pub byte_size: u64, @@ -78,6 +77,7 @@ pub struct PackedVectorLayout { pub ty: PackedVector, } +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MatrixLayout { pub byte_size: u64, @@ -85,6 +85,7 @@ pub struct MatrixLayout { pub ty: Matrix, } +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayLayout { pub byte_size: Option, @@ -96,6 +97,7 @@ pub struct ArrayLayout { } /// a sized or unsized struct type with 0 or more fields +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StructLayout { pub byte_size: Option, @@ -116,10 +118,11 @@ pub struct FieldLayout { } /// Enum of layout algorithms. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Repr { /// WGSL's layout algorithm /// https://www.w3.org/TR/WGSL/#alignment-and-size + #[default] Wgsl, /// Modified layout algorithm based on [`Repr::Wgsl`], but with different type /// alignments and array strides that make the resulting Layout match wgsl's @@ -204,6 +207,7 @@ impl TypeLayout { } } + /// Sets the alignment pub fn set_align(&mut self, align: U32PowerOf2) { let align = align.into(); match self { diff --git a/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs b/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs index 46b2d4a..10d84f9 100644 --- a/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs @@ -36,14 +36,14 @@ impl TypeLayoutRecipe { } } - // Returns a copy of self, but with all struct reprs changed to `repr`. + /// Returns a copy of self, but with all struct reprs changed to `repr`. pub fn to_unified_repr(&self, repr: Repr) -> Self { let mut this = self.clone(); this.change_all_repr(repr); this } - // Recursively changes all struct reprs to the given `repr`. + /// Recursively changes all struct reprs to the given `repr`. pub fn change_all_repr(&mut self, repr: Repr) { match self { TypeLayoutRecipe::Sized(s) => s.change_all_repr(repr), @@ -73,15 +73,12 @@ impl SizedType { } } - // Recursively changes all struct reprs to the given `repr`. + /// Recursively changes all struct reprs to the given `repr`. pub fn change_all_repr(&mut self, repr: Repr) { match self { SizedType::Struct(s) => s.change_all_repr(repr), - SizedType::Atomic(_) | - SizedType::PackedVec(_) | - SizedType::Vector(_) | - SizedType::Matrix(_) | - SizedType::Array(_) => { + SizedType::Array(s) => s.change_all_repr(repr), + SizedType::Atomic(_) | SizedType::PackedVec(_) | SizedType::Vector(_) | SizedType::Matrix(_) => { // No repr to change for these types. } } @@ -101,7 +98,7 @@ impl SizedStruct { /// This is expensive for structs as it calculates the byte size and align by traversing all fields recursively. pub fn byte_size_and_align(&self) -> (u64, U32PowerOf2) { self.field_offsets().struct_byte_size_and_align() } - // Recursively changes all struct reprs to the given `repr`. + /// Recursively changes all struct reprs to the given `repr`. pub fn change_all_repr(&mut self, repr: Repr) { self.repr = repr; for field in &mut self.fields { @@ -123,7 +120,7 @@ impl UnsizedStruct { /// This is expensive as it calculates the byte align by traversing all fields recursively. pub fn align(&self) -> U32PowerOf2 { self.field_offsets().last_field_offset_and_struct_align().1 } - // Recursively changes all struct reprs to the given `repr`. + /// Recursively changes all struct reprs to the given `repr`. pub fn change_all_repr(&mut self, repr: Repr) { self.repr = repr; for field in &mut self.sized_fields { @@ -935,8 +932,6 @@ mod tests { // Add a vec2 field - but with custom min align, which is ignored because of Repr::Packed let offset3 = calc.extend(8, U32PowerOf2::_8, None, Some(U32PowerOf2::_16), false); assert_eq!(offset3, 12); - // TODO(chronicl) not sure whether the alignment should stay 1 for a packesd struct - // with custom min align field. assert_eq!(calc.align(), U32PowerOf2::_1); } diff --git a/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs index 7392f91..968cfff 100644 --- a/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use crate::{ frontend::rust_types::type_layout::{ - ArrayLayout, FieldLayout, MatrixLayout, PackedVectorLayout, Repr, StructLayout, VectorLayout, DEFAULT_REPR, + ArrayLayout, FieldLayout, MatrixLayout, PackedVectorLayout, Repr, StructLayout, VectorLayout, }, ir, TypeLayout, }; @@ -10,9 +10,30 @@ use super::{ UnsizedStruct, Vector, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RecipeContains { + CustomFieldAlign, + CustomFieldSize, + PackedVector, +} + +impl std::fmt::Display for RecipeContains { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RecipeContains::CustomFieldAlign => write!(f, "custom field alignment attribute"), + RecipeContains::CustomFieldSize => write!(f, "custom field size attribute"), + RecipeContains::PackedVector => write!(f, "packed vector"), + } + } +} + impl TypeLayoutRecipe { - pub fn layout(&self) -> TypeLayout { self.layout_with_default_repr(DEFAULT_REPR) } + /// Returns the layout of this type recipe using `Repr::default` for top level types + /// that aren't structs. + pub fn layout(&self) -> TypeLayout { self.layout_with_default_repr(Repr::default()) } + /// Returns the layout of this type recipe using the given `default_repr` for top level types + /// that aren't structs. pub fn layout_with_default_repr(&self, default_repr: Repr) -> TypeLayout { match self { TypeLayoutRecipe::Sized(ty) => ty.layout(default_repr), @@ -20,8 +41,18 @@ impl TypeLayoutRecipe { TypeLayoutRecipe::RuntimeSizedArray(ty) => ty.layout(default_repr).into(), } } + + /// Checks whether `RecipeContains` is contained in this type recipe. + pub fn contains(&self, c: RecipeContains) -> bool { + match self { + TypeLayoutRecipe::Sized(ty) => ty.contains(c), + TypeLayoutRecipe::UnsizedStruct(ty) => ty.contains(c), + TypeLayoutRecipe::RuntimeSizedArray(ty) => ty.contains(c), + } + } } +#[allow(missing_docs)] impl SizedType { pub fn layout(&self, parent_repr: Repr) -> TypeLayout { match &self { @@ -30,11 +61,27 @@ impl SizedType { SizedType::Matrix(m) => m.layout(parent_repr).into(), SizedType::Array(a) => a.layout(parent_repr).into(), SizedType::PackedVec(v) => v.layout(parent_repr).into(), - SizedType::Struct(s) => s.layout(parent_repr).into(), + SizedType::Struct(s) => s.layout().into(), + } + } + + pub fn contains(&self, c: RecipeContains) -> bool { + match c { + RecipeContains::CustomFieldAlign | RecipeContains::CustomFieldSize | RecipeContains::PackedVector => { + match self { + SizedType::Vector(_) => false, + SizedType::Atomic(_) => false, + SizedType::Matrix(_) => false, + SizedType::Array(a) => a.contains(c), + SizedType::PackedVec(_) => c == RecipeContains::PackedVector, + SizedType::Struct(s) => s.contains(c), + } + } } } } +#[allow(missing_docs)] impl Vector { pub fn layout(&self, parent_repr: Repr) -> VectorLayout { VectorLayout { @@ -46,6 +93,7 @@ impl Vector { } } +#[allow(missing_docs)] impl Matrix { pub fn layout(&self, parent_repr: Repr) -> MatrixLayout { MatrixLayout { @@ -56,6 +104,7 @@ impl Matrix { } } +#[allow(missing_docs)] impl Atomic { pub fn layout(&self, parent_repr: Repr) -> VectorLayout { // Atomic types are represented as vectors of length 1. @@ -76,6 +125,7 @@ impl PackedVector { } } +#[allow(missing_docs)] impl SizedArray { pub fn layout(&self, parent_repr: Repr) -> ArrayLayout { ArrayLayout { @@ -86,14 +136,23 @@ impl SizedArray { len: Some(self.len.get()), } } + + pub fn contains(&self, c: RecipeContains) -> bool { + match c { + RecipeContains::CustomFieldAlign | RecipeContains::CustomFieldSize | RecipeContains::PackedVector => { + self.element.contains(c) + } + } + } } +#[allow(missing_docs)] impl SizedStruct { - pub fn layout(&self, parent_repr: Repr) -> StructLayout { + pub fn layout(&self) -> StructLayout { let mut field_offsets = self.field_offsets(); let fields = (&mut field_offsets) .zip(self.fields()) - .map(|(offset, field)| sized_field_to_field_layout(field, offset, parent_repr)) + .map(|(offset, field)| sized_field_to_field_layout(field, offset, self.repr)) .collect::>(); let (byte_size, align) = field_offsets.struct_byte_size_and_align(); @@ -105,6 +164,23 @@ impl SizedStruct { fields, } } + + pub fn contains(&self, c: RecipeContains) -> bool { + let mut contains = false; + match c { + RecipeContains::CustomFieldAlign => { + contains |= self.fields.iter().any(|f| f.custom_min_align.is_some()); + } + RecipeContains::CustomFieldSize => { + contains |= self.fields.iter().any(|f| f.custom_min_size.is_some()); + } + RecipeContains::PackedVector => {} + } + for field in self.fields.iter() { + contains |= field.ty.contains(c); + } + contains + } } fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> FieldLayout { @@ -121,6 +197,7 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F } } +#[allow(missing_docs)] impl UnsizedStruct { pub fn layout(&self) -> StructLayout { let mut field_offsets = self.field_offsets(); @@ -149,8 +226,27 @@ impl UnsizedStruct { fields, } } + + pub fn contains(&self, c: RecipeContains) -> bool { + let mut contains = false; + match c { + RecipeContains::CustomFieldAlign => { + contains |= self.sized_fields.iter().any(|f| f.custom_min_align.is_some()); + contains |= self.last_unsized.custom_min_align.is_some(); + } + RecipeContains::CustomFieldSize => { + contains |= self.sized_fields.iter().any(|f| f.custom_min_size.is_some()); + } + RecipeContains::PackedVector => {} + } + for field in self.sized_fields.iter() { + contains |= field.ty.contains(c); + } + contains + } } +#[allow(missing_docs)] impl RuntimeSizedArray { pub fn layout(&self, parent_repr: Repr) -> ArrayLayout { ArrayLayout { @@ -161,4 +257,12 @@ impl RuntimeSizedArray { len: None, } } + + pub fn contains(&self, c: RecipeContains) -> bool { + match c { + RecipeContains::CustomFieldAlign | RecipeContains::CustomFieldSize | RecipeContains::PackedVector => { + self.element.contains(c) + } + } + } } diff --git a/shame/src/ir/pipeline/wip_pipeline.rs b/shame/src/ir/pipeline/wip_pipeline.rs index c5ba67a..c694b37 100644 --- a/shame/src/ir/pipeline/wip_pipeline.rs +++ b/shame/src/ir/pipeline/wip_pipeline.rs @@ -33,7 +33,7 @@ use crate::{ error::InternalError, rust_types::{ len::x3, - type_layout::{self, recipe, StructLayout, DEFAULT_REPR}, + type_layout::{self, recipe, StructLayout}, }, }, ir::{ @@ -356,7 +356,7 @@ impl WipPushConstantsField { let sized_struct: recipe::SizedStruct = sized_struct .try_into() .map_err(|e| InternalError::new(true, format!("{e}")))?; - let layout = sized_struct.layout(DEFAULT_REPR); + let layout = sized_struct.layout(); let mut ranges = ByteRangesPerStage::default(); diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 0b202ea..a81478e 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -164,14 +164,6 @@ pub fn impl_for_struct( "`#[gpu_repr(packed)]` structs do not support `#[align(N)]` attributes" ); } - // TODO(chronicl) decide on whether size attribute is allowed. Will have to be adjusted in - // LayoutCalculator too! - // if fwa.size.is_some() { - // bail!( - // field.span(), - // "`#[gpu_repr(packed)]` structs do not support `#[size(N)]` attributes" - // ); - // } } Repr::Wgsl => {} } From b78cba8b813a1103b628814629de2c92df42aaa1 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 26 Jul 2025 18:43:16 +0200 Subject: [PATCH 147/182] disable print in tests --- .../frontend/rust_types/type_layout/compatible_with.rs | 2 +- shame/src/frontend/rust_types/type_layout/eq.rs | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 50b9f33..3beee6c 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -539,7 +539,7 @@ mod tests { use shame as sm; use shame::{aliases::*, GpuLayout}; - const PRINT: bool = true; + const PRINT: bool = false; macro_rules! is_struct_mismatch { ($result:expr, $as_error:ident, $mismatch:pat) => { diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index f12464b..4e75432 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -549,7 +549,7 @@ mod tests { use shame::{CpuLayout, GpuLayout, gpu_layout, cpu_layout}; use crate::aliases::*; - const PRINT: bool = true; + const PRINT: bool = false; #[derive(Clone, Copy)] #[repr(C)] @@ -699,19 +699,12 @@ mod tests { #[test] fn test_stride_mismatch() { let _guard = enable_color(); - - if PRINT { - println!( - "The two error messages are what is produced when non-structs, in this case arrays, are the top level types\n" - ); - } check_mismatch::>, [f32x3_align4; 4]>(); } #[test] fn test_nested_stride_mismatch() { let _guard = enable_color(); - check_mismatch::>, sm::Size<2>>, [[f32x3_align4; 4]; 2]>(); } From 96e16cd20b34cd30f78978d936e579485ab13ffd Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 26 Jul 2025 19:27:40 +0200 Subject: [PATCH 148/182] new layout mismatch test --- .../rust_types/type_layout/display.rs | 8 +++- .../src/frontend/rust_types/type_layout/eq.rs | 39 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs index 041f3d1..3db7653 100644 --- a/shame/src/frontend/rust_types/type_layout/display.rs +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -23,7 +23,13 @@ impl TypeLayout { use TypeLayout::*; match &self { - Vector(v) => v.ty.to_string(), + Vector(v) => { + if v.debug_is_atomic { + format!("atomic<{}>", v.ty.scalar) + } else { + v.ty.to_string() + } + } PackedVector(v) => v.ty.to_string(), Matrix(m) => m.ty.to_string(), Array(a) => a.short_name(), diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index 4e75432..f77e21d 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -361,7 +361,7 @@ impl CheckEqLayoutMismatch { mismatch: TopLevelMismatch::Type, .. } => { - writeln!(f, "type of {} is different.", field_left.name)?; + writeln!(f, "type of `{}` is different.", field_left.name)?; (Some(field_index), LayoutInfo::NONE) } StructMismatch::FieldLayout { @@ -386,7 +386,7 @@ impl CheckEqLayoutMismatch { // the inner type is the one that has mismatching byte size. (Some(field_index), LayoutInfo::NONE) } else { - writeln!(f, "byte size of {} is different.", field_left.name)?; + writeln!(f, "byte size of `{}` is different.", field_left.name)?; (Some(field_index), LayoutInfo::SIZE) } } @@ -549,7 +549,7 @@ mod tests { use shame::{CpuLayout, GpuLayout, gpu_layout, cpu_layout}; use crate::aliases::*; - const PRINT: bool = false; + const PRINT: bool = true; #[derive(Clone, Copy)] #[repr(C)] @@ -728,4 +728,37 @@ mod tests { } check_mismatch::(); } + + #[test] + fn test_struct_in_struct_mismatch() { + let _guard = enable_color(); + + #[derive(GpuLayout)] + pub struct Inner { + x: f32x1, + y: f32x1, + } + + #[derive(GpuLayout)] + pub struct Outer { + inner: sm::Struct, + z: u32x1, + } + + #[derive(CpuLayout)] + #[repr(C)] + pub struct InnerCpu { + x: f32, + y: u32, // Type mismatch here + } + + #[derive(CpuLayout)] + #[repr(C)] + pub struct OuterCpu { + inner: InnerCpu, + z: u32, + } + + check_mismatch::(); + } } From d154c3469254b41f66fbcfee37e0aa73526b7427 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 26 Jul 2025 22:09:58 +0200 Subject: [PATCH 149/182] remove unused to_string methods --- shame/src/frontend/rust_types/type_layout/display.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs index 3db7653..6e8990d 100644 --- a/shame/src/frontend/rust_types/type_layout/display.rs +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -37,12 +37,6 @@ impl TypeLayout { } } - pub(crate) fn to_string_with_layout_information(&self, layout_info: LayoutInfo) -> Result { - let mut s = String::new(); - self.write(&mut s, layout_info)?; - Ok(s) - } - pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { use TypeLayout::*; @@ -76,12 +70,6 @@ impl StructLayout { /// a short name for this `StructLayout`, useful for printing inline pub fn short_name(&self) -> String { self.name.to_string() } - pub(crate) fn to_string_with_layout_info(&self, layout_info: LayoutInfo) -> Result { - let mut s = String::new(); - self.write(&mut s, layout_info)?; - Ok(s) - } - pub(crate) fn writer(&self, layout_info: LayoutInfo) -> StructWriter<'_> { StructWriter::new(self, layout_info) } pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { From d1a2eec328301c1c53768c50965163e925c76603 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 26 Jul 2025 22:13:26 +0200 Subject: [PATCH 150/182] rearange method --- shame/src/frontend/rust_types/type_layout/display.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs index 6e8990d..890bd59 100644 --- a/shame/src/frontend/rust_types/type_layout/display.rs +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -167,8 +167,6 @@ impl<'a> StructWriter<'a> { this } - pub(crate) fn layout_info_offset(&self) -> usize { self.layout_info_offset } - /// By setting `max_fields` to `Some(n)`, the writer will adjust Self::layout_info_offset /// to only take into account the first `n` fields of the struct. pub(crate) fn set_layout_info_offset_auto(&mut self, max_fields: Option) { @@ -189,6 +187,8 @@ impl<'a> StructWriter<'a> { self.layout_info_offset = self.layout_info_offset.max(min_layout_info_offset) } + pub(crate) fn layout_info_offset(&self) -> usize { self.layout_info_offset } + pub(crate) fn tab(&self) -> &'static str { self.tab } fn struct_declaration(&self) -> String { format!("struct {} {{", self.s.name) } From c272489d00a2c3faf793228419f8df11b8a53d31 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sat, 26 Jul 2025 22:45:24 +0200 Subject: [PATCH 151/182] more tests and docs --- .../src/frontend/rust_types/type_layout/eq.rs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index f77e21d..f51c869 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -28,9 +28,9 @@ use super::*; /// a `TopLevelMismatch::ArrayStride` with the layout of `Array`. /// /// A field of nested arrays in a struct is handled in the same way by -/// `LayoutMismatch::Struct` containing the field index and `FieldLayout`, which let's us access the outer -/// most array, and a `TopLevelMismatch`, which let's us access the inner type layout -/// where the mismatch is happening. +/// `LayoutMismatch::Struct` containing the mismatching field index and `FieldLayout`, +/// which let's us access the outer most array, and a `TopLevelMismatch`, +/// which let's us access the inner type layout where the mismatch is happening. #[derive(Debug, Clone)] pub enum LayoutMismatch { TopLevel { @@ -761,4 +761,23 @@ mod tests { check_mismatch::(); } + + #[test] + fn test_field_count_mismatch() { + let _guard = enable_color(); + + #[derive(GpuLayout)] + pub struct H { + a: f32x1, + b: u32x1, + } + #[derive(CpuLayout)] + #[repr(C)] + pub struct HCpu { + a: f32, + b: u32, + c: f32, // Extra field + } + check_mismatch::(); + } } From beffd64307bc12696c51ccdf08f09da3b855b1e3 Mon Sep 17 00:00:00 2001 From: chronicl Date: Sun, 27 Jul 2025 16:24:01 +0200 Subject: [PATCH 152/182] comment improved --- shame/src/frontend/rust_types/type_layout/eq.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index f51c869..50f1698 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -446,8 +446,8 @@ impl CheckEqLayoutMismatch { let mut writer_left = struct_left.writer(layout_info); let mut writer_right = struct_right.writer(layout_info); - // Make sure layout info offset only takes account the fields before and including the mismatch, - // because those are the only fields that will be written below. + // Make it so layout info offset only takes into account the fields before and + // including the mismatch, because those are the only fields that will be written below. if let Some(mismatch_field_index) = mismatch_field_index { let max_fields = Some(mismatch_field_index + 1); writer_left.set_layout_info_offset_auto(max_fields); From 291cc2698a9284620af8ecac68545418f2c0a41c Mon Sep 17 00:00:00 2001 From: chronicl Date: Mon, 4 Aug 2025 04:39:32 +0200 Subject: [PATCH 153/182] Replace compatible_with::AddressSpace with existing BufferAddressSpace --- shame/src/frontend/encoding/buffer.rs | 29 ++++- .../rust_types/type_layout/compatible_with.rs | 117 ++++++++---------- 2 files changed, 75 insertions(+), 71 deletions(-) diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index c4a7dab..418bbbc 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -30,9 +30,32 @@ use super::binding::Binding; /// Implemented by the marker types /// - [`mem::Uniform`] /// - [`mem::Storage`] -pub trait BufferAddressSpace: AddressSpace + SupportsAccess {} -impl BufferAddressSpace for mem::Uniform {} -impl BufferAddressSpace for mem::Storage {} +pub trait BufferAddressSpace: AddressSpace + SupportsAccess { + /// Either Storage or Uniform address space. + const BUFFER_ADDRESS_SPACE: BufferAddressSpaceEnum; +} +/// Either Storage or Uniform address space. +#[derive(Debug, Clone, Copy)] +pub enum BufferAddressSpaceEnum { + /// Storage address space + Storage, + /// Uniform address space + Uniform, +} +impl BufferAddressSpace for mem::Uniform { + const BUFFER_ADDRESS_SPACE: BufferAddressSpaceEnum = BufferAddressSpaceEnum::Uniform; +} +impl BufferAddressSpace for mem::Storage { + const BUFFER_ADDRESS_SPACE: BufferAddressSpaceEnum = BufferAddressSpaceEnum::Storage; +} +impl std::fmt::Display for BufferAddressSpaceEnum { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BufferAddressSpaceEnum::Storage => write!(f, "storage address space"), + BufferAddressSpaceEnum::Uniform => write!(f, "uniform address space"), + } + } +} /// A read-only buffer binding, for writeable buffers and atomics use [`BufferRef`] instead. /// diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 3beee6c..7f121e1 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -4,14 +4,17 @@ use crate::{ any::layout::StructLayout, call_info, common::prettify::{set_color, UnwrapOrStr}, - frontend::rust_types::type_layout::{ - display::LayoutInfo, - eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, - recipe::to_layout::RecipeContains, - ArrayLayout, + frontend::{ + encoding::buffer::BufferAddressSpaceEnum, + rust_types::type_layout::{ + display::LayoutInfo, + eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, + recipe::to_layout::RecipeContains, + ArrayLayout, + }, }, ir::{ir_type::max_u64_po2_dividing, recording::Context}, - mem, Language, TypeLayout, + mem, BufferAddressSpace, Language, TypeLayout, }; use super::{recipe::TypeLayoutRecipe, Repr}; @@ -51,23 +54,24 @@ pub struct TypeLayoutCompatibleWith { _phantom: std::marker::PhantomData, } -impl TypeLayoutCompatibleWith { +impl TypeLayoutCompatibleWith { pub fn try_from(language: Language, recipe: TypeLayoutRecipe) -> Result { - let address_space = AS::ADDRESS_SPACE; + let address_space = AS::BUFFER_ADDRESS_SPACE; let layout = recipe.layout(); match (language, address_space, layout.byte_size()) { // Must be sized in wgsl's uniform address space - (Language::Wgsl, AddressSpaceEnum::Uniform, None) => { + (Language::Wgsl, BufferAddressSpaceEnum::Uniform, None) => { return Err(RequirementsNotSatisfied::MustBeSized(recipe, language, address_space).into()); } - (Language::Wgsl, AddressSpaceEnum::Uniform, Some(_)) | (Language::Wgsl, AddressSpaceEnum::Storage, _) => {} + (Language::Wgsl, BufferAddressSpaceEnum::Uniform, Some(_)) + | (Language::Wgsl, BufferAddressSpaceEnum::Storage, _) => {} } // Check that the recipe is representable in the target language. // See `TypeLayoutCompatibleWith` docs for more details on what it means to be representable. match (language, address_space) { - (Language::Wgsl, AddressSpaceEnum::Storage | AddressSpaceEnum::Uniform) => { + (Language::Wgsl, BufferAddressSpaceEnum::Storage | BufferAddressSpaceEnum::Uniform) => { // We match like this, so that future additions to `RecipeContains` lead us here. match RecipeContains::CustomFieldAlign { // supported in wgsl @@ -111,11 +115,11 @@ impl TypeLayoutCompatibleWith { // Check that the type layout satisfies the requirements of the address space match (language, address_space) { - (Language::Wgsl, AddressSpaceEnum::Storage) => { + (Language::Wgsl, BufferAddressSpaceEnum::Storage) => { // As long as the recipe is representable in wgsl, it satifies the storage address space requirements. // We already checked that the recipe is representable in wgsl above. } - (Language::Wgsl, AddressSpaceEnum::Uniform) => { + (Language::Wgsl, BufferAddressSpaceEnum::Uniform) => { // Repr::WgslUniform is made for exactly this purpose: to check that the type layout // satisfies the requirements of wgsl's uniform address space. let recipe_unified = recipe.to_unified_repr(Repr::WgslUniform); @@ -147,29 +151,6 @@ impl TypeLayoutCompatibleWith { } } -pub trait AddressSpace { - const ADDRESS_SPACE: AddressSpaceEnum; -} -impl AddressSpace for Storage { - const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::Storage; -} -impl AddressSpace for Uniform { - const ADDRESS_SPACE: AddressSpaceEnum = AddressSpaceEnum::Uniform; -} -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum AddressSpaceEnum { - Storage, - Uniform, -} -impl std::fmt::Display for AddressSpaceEnum { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AddressSpaceEnum::Storage => write!(f, "storage address space"), - AddressSpaceEnum::Uniform => write!(f, "uniform address space"), - } - } -} - #[derive(thiserror::Error, Debug, Clone)] pub enum AddressSpaceError { #[error("{0}")] @@ -183,9 +164,9 @@ pub enum NotRepresentable { #[error("{0}")] LayoutError(LayoutError), #[error("{0} contains a {3}, which is not allowed in {1}'s {2}.")] - MayNotContain(TypeLayoutRecipe, Language, AddressSpaceEnum, RecipeContains), + MayNotContain(TypeLayoutRecipe, Language, BufferAddressSpaceEnum, RecipeContains), #[error("Unknown layout error occured for {0} in {1}.")] - UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), + UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), } #[derive(thiserror::Error, Debug, Clone)] @@ -196,9 +177,9 @@ pub enum RequirementsNotSatisfied { "The size of `{0}` on the gpu is not known at compile time. {1}'s {2} \ requires that the size of {0} on the gpu is known at compile time." )] - MustBeSized(TypeLayoutRecipe, Language, AddressSpaceEnum), + MustBeSized(TypeLayoutRecipe, Language, BufferAddressSpaceEnum), #[error("Unknown layout error occured for {0} in {1}.")] - UnknownLayoutError(TypeLayoutRecipe, AddressSpaceEnum), + UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), } #[derive(Debug, Clone)] @@ -210,7 +191,7 @@ pub struct LayoutError { /// to fit `NotRepresentable` or `RequirementsNotSatisfied`. kind: LayoutErrorKind, language: Language, - address_space: AddressSpaceEnum, + address_space: BufferAddressSpaceEnum, colored: bool, } @@ -230,8 +211,8 @@ impl LayoutError { Language::Wgsl => "wgsl", }, LayoutErrorKind::RequirementsNotSatisfied => match (self.language, self.address_space) { - (Language::Wgsl, AddressSpaceEnum::Storage) => "wgsl's storage address space", - (Language::Wgsl, AddressSpaceEnum::Uniform) => "wgsl's uniform address space", + (Language::Wgsl, BufferAddressSpaceEnum::Storage) => "wgsl's storage address space", + (Language::Wgsl, BufferAddressSpaceEnum::Uniform) => "wgsl's uniform address space", }, } } @@ -444,13 +425,13 @@ fn write_struct_mismatch( "- increase the offset of `{field_name}` until it is divisible by {expected_align} by making previous fields larger or adding fields before it" )?; match (error.kind, error.language, error.address_space) { - (LayoutErrorKind::RequirementsNotSatisfied, Language::Wgsl, AddressSpaceEnum::Uniform) => { + (LayoutErrorKind::RequirementsNotSatisfied, Language::Wgsl, BufferAddressSpaceEnum::Uniform) => { writeln!(f, "- use a storage binding instead of a uniform binding")?; } ( LayoutErrorKind::NotRepresentable | LayoutErrorKind::RequirementsNotSatisfied, Language::Wgsl, - AddressSpaceEnum::Storage | AddressSpaceEnum::Uniform, + BufferAddressSpaceEnum::Storage | BufferAddressSpaceEnum::Uniform, ) => {} } writeln!(f)?; @@ -458,18 +439,18 @@ fn write_struct_mismatch( match error.language { Language::Wgsl => { match error.address_space { - AddressSpaceEnum::Uniform => writeln!( + BufferAddressSpaceEnum::Uniform => writeln!( f, "In {}, structs, arrays and array elements must be at least 16 byte aligned.", error.context() )?, - AddressSpaceEnum::Storage => {} + BufferAddressSpaceEnum::Storage => {} } match (error.kind, error.address_space) { ( LayoutErrorKind::RequirementsNotSatisfied, - AddressSpaceEnum::Uniform | AddressSpaceEnum::Storage, + BufferAddressSpaceEnum::Uniform | BufferAddressSpaceEnum::Storage, ) => writeln!( f, "More info about the wgsl's {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", @@ -483,9 +464,9 @@ fn write_struct_mismatch( } } } - StructMismatch::FieldCount | - StructMismatch::FieldName { .. } | - StructMismatch::FieldLayout { + StructMismatch::FieldCount + | StructMismatch::FieldName { .. } + | StructMismatch::FieldLayout { mismatch: TopLevelMismatch::Type, .. } => { @@ -542,23 +523,23 @@ mod tests { const PRINT: bool = false; macro_rules! is_struct_mismatch { - ($result:expr, $as_error:ident, $mismatch:pat) => { + ($result:expr, $as_error:ident, $mismatch:pat) => {{ + if let Err(e) = &$result + && PRINT { - if let Err(e) = &$result && PRINT { - println!("{e}"); - } - matches!( - $result, - Err(AddressSpaceError::$as_error($as_error::LayoutError(LayoutError { - mismatch: LayoutMismatch::Struct { - mismatch: $mismatch, - .. - }, - .. - }))) - ) + println!("{e}"); } - }; + matches!( + $result, + Err(AddressSpaceError::$as_error($as_error::LayoutError(LayoutError { + mismatch: LayoutMismatch::Struct { + mismatch: $mismatch, + .. + }, + .. + }))) + ) + }}; } fn enable_color() -> Option, ThreadIsAlreadyEncoding>> { @@ -705,7 +686,7 @@ mod tests { AddressSpaceError::RequirementsNotSatisfied(RequirementsNotSatisfied::MustBeSized( _, Language::Wgsl, - AddressSpaceEnum::Uniform + BufferAddressSpaceEnum::Uniform )) )); @@ -731,7 +712,7 @@ mod tests { AddressSpaceError::NotRepresentable(NotRepresentable::MayNotContain( _, Language::Wgsl, - AddressSpaceEnum::Storage, + BufferAddressSpaceEnum::Storage, RecipeContains::PackedVector )) )); From ab5182d8f6d834023579aac3b2ff1c7af3bea1eb Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sat, 18 Oct 2025 21:24:47 +0200 Subject: [PATCH 154/182] making `U32PowerOf2` part of public api --- shame/src/lib.rs | 2 ++ shame/tests/test_layout.rs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 6b9223c..b7fea02 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -319,6 +319,8 @@ pub use frontend::rust_types::layout_traits::gpu_layout; pub use frontend::rust_types::layout_traits::CpuLayout; pub use frontend::rust_types::layout_traits::cpu_layout; pub use frontend::rust_types::type_layout::TypeLayout; +pub use common::po2::U32PowerOf2; +pub use common::po2::NotAU32PowerOf2; // derived traits pub use frontend::rust_types::type_traits::GpuStore; diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 7f1f589..2843182 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -163,7 +163,7 @@ struct f32x2_align4(pub [f32; 2]); impl CpuLayout for f32x2_align4 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_align(shame::any::U32PowerOf2::_4); + layout.set_align(shame::U32PowerOf2::_4); layout } } @@ -175,7 +175,7 @@ struct f32x4_align4(pub [f32; 4]); impl CpuLayout for f32x4_align4 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_align(shame::any::U32PowerOf2::_4); + layout.set_align(shame::U32PowerOf2::_4); layout } } @@ -192,7 +192,7 @@ static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); impl CpuLayout for f32x3_align4 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_align(shame::any::U32PowerOf2::_4.into()); + layout.set_align(shame::U32PowerOf2::_4); layout } } @@ -354,7 +354,7 @@ fn external_vec_type() { impl CpuLayoutExt for glam::Vec3 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_align(sm::any::U32PowerOf2::_4); + layout.set_align(sm::U32PowerOf2::_4); layout } } From c492a1f854199f91b91721ee44c4fcaac788fe62 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sat, 18 Oct 2025 21:27:43 +0200 Subject: [PATCH 155/182] rust 1.90.0 fix --- shame_derive/src/derive_layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index a81478e..5f4b35a 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -557,7 +557,7 @@ pub fn impl_for_struct( Ok(t) => t, Err(actual_len) => { let any = push_wrong_amount_of_args_error(actual_len, EXPECTED_LEN, #re::call_info!()); - [any; EXPECTED_LEN] + std::array::from_fn(|_| any) } }; Self { From b35b8f3e3359f19710c1add95b8faecfd449e52e Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sat, 18 Oct 2025 23:51:59 +0200 Subject: [PATCH 156/182] added `rust_layout_with_shame_semantics` helper --- shame/tests/test_layout.rs | 57 +++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 2843182..8a54b25 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -1,5 +1,6 @@ #![allow(non_camel_case_types, unused)] use pretty_assertions::{assert_eq, assert_ne}; +use sm::__private::proc_macro_reexports::CpuAligned; use shame::{self as sm, cpu_layout, gpu_layout}; use sm::{aliases::*, CpuLayout, GpuLayout}; @@ -147,25 +148,27 @@ struct f32x4_cpu(pub [f32; 4]); struct f32x3_cpu(pub [f32; 3]); impl CpuLayout for f32x3_cpu { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { + // TODO(release): replace this with `rust_layout_with_shame_semantics::()` + // and find a proper solution to the consequences. Its size is 16, and not 12. + let mut layout = gpu_layout::(); + layout.set_align(Self::CPU_ALIGNMENT); + layout + } } #[derive(Clone, Copy)] #[repr(C, align(8))] struct f32x2_cpu(pub [f32; 2]); impl CpuLayout for f32x2_cpu { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { rust_layout_with_shame_semantics::() } } #[derive(Clone, Copy)] #[repr(C)] struct f32x2_align4(pub [f32; 2]); impl CpuLayout for f32x2_align4 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.set_align(shame::U32PowerOf2::_4); - layout - } + fn cpu_layout() -> shame::TypeLayout { rust_layout_with_shame_semantics::() } } #[derive(Clone, Copy)] @@ -173,11 +176,7 @@ impl CpuLayout for f32x2_align4 { struct f32x4_align4(pub [f32; 4]); impl CpuLayout for f32x4_align4 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.set_align(shame::U32PowerOf2::_4); - layout - } + fn cpu_layout() -> shame::TypeLayout { rust_layout_with_shame_semantics::() } } #[derive(Clone, Copy)] @@ -191,8 +190,10 @@ static_assertions::assert_eq_align!(glam::Vec4, f32x4_cpu); impl CpuLayout for f32x3_align4 { fn cpu_layout() -> shame::TypeLayout { + // TODO(release): replace this with `rust_layout_with_shame_semantics::()` + // and find a proper solution to the consequences. Its size is 16, and not 12. let mut layout = gpu_layout::(); - layout.set_align(shame::U32PowerOf2::_4); + layout.set_align(Self::CPU_ALIGNMENT); layout } } @@ -202,7 +203,13 @@ impl CpuLayout for f32x3_align4 { struct f32x3_size32(pub [f32; 3], [u8; 20]); impl CpuLayout for f32x3_size32 { - fn cpu_layout() -> shame::TypeLayout { gpu_layout::() } + fn cpu_layout() -> shame::TypeLayout { + // TODO(release): replace this with `rust_layout_with_shame_semantics::()` + // and find a proper solution to the consequences. Its size is 16, and not 12. + let mut layout = gpu_layout::(); + layout.set_align(Self::CPU_ALIGNMENT); + layout + } } @@ -338,6 +345,7 @@ fn layouts_mismatch() { fn external_vec_type() { // using duck-traiting just so that the proc-macro uses `CpuLayoutExt::layout()` pub mod my_mod { + use super::rust_layout_with_shame_semantics; use shame::gpu_layout; use shame as sm; use sm::aliases::*; @@ -352,11 +360,7 @@ fn external_vec_type() { } impl CpuLayoutExt for glam::Vec3 { - fn cpu_layout() -> shame::TypeLayout { - let mut layout = gpu_layout::(); - layout.set_align(sm::U32PowerOf2::_4); - layout - } + fn cpu_layout() -> shame::TypeLayout { rust_layout_with_shame_semantics::() } } } @@ -452,3 +456,18 @@ fn external_vec_type() { // enum __ where OnGpu: sm::VertexLayout {} } } + +/// helper for defining a `TypeLayout` for a cpu type that +/// represents a `GpuSemantics` on the Gpu, but has alignment and size of `Layout` on the Cpu +pub fn rust_layout_with_shame_semantics() -> sm::TypeLayout { + let mut layout = sm::gpu_layout::(); + + layout.set_align(CpuType::CPU_ALIGNMENT); + layout.set_byte_size(Some(size_of::() as u64)); + + // these are just here because we are testing + assert_eq!(layout.align().as_u32(), align_of::() as u32); + assert_eq!(layout.byte_size().map(|x| x as _), CpuType::CPU_SIZE); + + layout +} From cd7c16d04cd881ba547aed9e1e2f9bad15b99403 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sat, 18 Oct 2025 23:52:32 +0200 Subject: [PATCH 157/182] fix warnings --- examples/shame_wgpu/src/lib.rs | 3 +++ shame/src/common/po2.rs | 1 + shame/src/lib.rs | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/shame_wgpu/src/lib.rs b/examples/shame_wgpu/src/lib.rs index 40e58d9..7177494 100644 --- a/examples/shame_wgpu/src/lib.rs +++ b/examples/shame_wgpu/src/lib.rs @@ -1,6 +1,9 @@ //! shame wgpu integration //! //! bind-group and pipeline glue code +#![allow(mismatched_lifetime_syntaxes)] +#![deny(unsafe_code)] + pub use shame::*; pub mod bind_group; pub mod binding; diff --git a/shame/src/common/po2.rs b/shame/src/common/po2.rs index 6382071..ccbfae8 100644 --- a/shame/src/common/po2.rs +++ b/shame/src/common/po2.rs @@ -91,6 +91,7 @@ impl U32PowerOf2 { pub const fn max(self, other: Self) -> Self { if self as u32 > other as u32 { self } else { other } } } +#[allow(missing_docs)] #[derive(Debug)] pub struct NotAU32PowerOf2(u32); diff --git a/shame/src/lib.rs b/shame/src/lib.rs index b7fea02..5d0d1b2 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -2,7 +2,11 @@ #![forbid(unsafe_code)] //#![warn(clippy::cast_lossless)] #![deny(missing_docs)] -#![allow(clippy::match_like_matches_macro, clippy::diverging_sub_expression)] +#![allow( + mismatched_lifetime_syntaxes, + clippy::match_like_matches_macro, + clippy::diverging_sub_expression +)] #![allow(unused)] mod backend; From fb3137c02e51316822e81f7794fb4fbcd016c207 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sun, 19 Oct 2025 12:14:06 +0200 Subject: [PATCH 158/182] preparing changes to `TypeLayout`s size/align mutation api --- shame/src/common/proc_macro_utils.rs | 4 +- .../src/frontend/rust_types/type_layout/eq.rs | 2 +- .../frontend/rust_types/type_layout/mod.rs | 64 ++++++++++++------- .../type_layout/recipe/to_layout.rs | 2 +- shame/tests/test_layout.rs | 41 +++++++++++- 5 files changed, 85 insertions(+), 28 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 06035bd..320fe1a 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -109,7 +109,7 @@ pub fn repr_c_struct_layout( .map(|(field, offset, size)| (field, *offset as u64, *size as u64)) .map(|(mut field, offset, size)| { let mut layout = field.layout.clone(); - layout.set_byte_size(new_size(field.layout.byte_size(), Some(size))); + layout.set_byte_size_if_some(new_size(field.layout.byte_size(), Some(size))); FieldLayout { rel_byte_offset: offset, name: field.name.into(), @@ -119,7 +119,7 @@ pub fn repr_c_struct_layout( .chain(std::iter::once({ last_field .layout - .set_byte_size(new_size(last_field.layout.byte_size(), last_field_size)); + .set_byte_size_if_some(new_size(last_field.layout.byte_size(), last_field_size)); FieldLayout { rel_byte_offset: last_field_offset, name: last_field.name.into(), diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index 50f1698..d16c62f 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -572,7 +572,7 @@ mod tests { impl CpuLayout for f32x3_size16 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_byte_size(Some(16)); + layout.set_byte_size_if_some(Some(16)); layout } } diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 3dc9380..7726e4e 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -176,8 +176,45 @@ impl TypeLayout { } } +/// mutable reference to the alignment requirement of the represented type. + /// + /// may allocate a new `Rc` (via `Rc::make_mut`) for `Array`/`Struct` layouts + pub fn align_mut(&mut self) -> &mut U32PowerOf2 { + match self { + TypeLayout::Vector(v) => &mut v.align, + TypeLayout::Matrix(m) => &mut m.align, + TypeLayout::PackedVector(v) => &mut v.align, + TypeLayout::Array(a) => &mut Rc::make_mut(a).align, + TypeLayout::Struct(s) => &mut Rc::make_mut(s).align, + } + } + + /// mutable access to the `byte_size` if it exists + pub fn try_byte_size_mut(&mut self) -> Option<&mut u64> { + match self.removable_byte_size_mut() { + Ok(removable) => removable.as_mut(), + Err(fixed) => Some(fixed), + } + } + + /// mutable access to the size of a `self` + /// + /// returns + /// - `Ok(&mut option)` if `self`'s represented type can exist in an unsized configuration (e.g. struct, array) + /// - `Err(&mut size)` if `self`'s represented type is always sized (e.g. vector, matrix) + pub fn removable_byte_size_mut(&mut self) -> Result<&mut Option, &mut u64> { + match self { + TypeLayout::Vector(v) => Err(&mut v.byte_size), + TypeLayout::Matrix(m) => Err(&mut m.byte_size), + TypeLayout::PackedVector(v) => Err(&mut v.byte_size), + TypeLayout::Array(a) => Ok(&mut Rc::make_mut(a).byte_size), + TypeLayout::Struct(s) => Ok(&mut Rc::make_mut(s).byte_size), + } + } + + // TODO: remove, this function does not always behave as specified (struct being sized, `byte_size` = None) /// If self is sized and `byte_size` is None, the size is not overwritten. - pub fn set_byte_size(&mut self, byte_size: Option) { + pub fn set_byte_size_if_some(&mut self, byte_size: Option) { match self { TypeLayout::Vector(v) => { if let Some(size) = byte_size { @@ -196,10 +233,12 @@ impl TypeLayout { } TypeLayout::Array(a) => { let mut array = (**a).clone(); +// FIXME: confusing: this is always written, even if self is sized and `byte_size` is None array.byte_size = byte_size; *a = Rc::new(array); } TypeLayout::Struct(s) => { +// FIXME: confusing: this is always written, even if self is sized and `byte_size` is None let mut struct_ = (**s).clone(); struct_.byte_size = byte_size; *s = Rc::new(struct_); @@ -209,28 +248,7 @@ impl TypeLayout { /// Sets the alignment pub fn set_align(&mut self, align: U32PowerOf2) { - let align = align.into(); - match self { - TypeLayout::Vector(v) => { - v.align = align; - } - TypeLayout::Matrix(m) => { - m.align = align; - } - TypeLayout::PackedVector(v) => { - v.align = align; - } - TypeLayout::Array(a) => { - let mut array = (**a).clone(); - array.align = align; - *a = Rc::new(array); - } - TypeLayout::Struct(s) => { - let mut struct_ = (**s).clone(); - struct_.align = align; - *s = Rc::new(struct_); - } - } + *self.align_mut() = align } // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings diff --git a/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs index 968cfff..bd89aa4 100644 --- a/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs @@ -187,7 +187,7 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F let mut ty = field.ty.layout(repr); // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account // custom_min_align and custom_min_size, but field.byte_size and field.align do. - ty.set_byte_size(Some(field.byte_size(repr))); + ty.set_byte_size_if_some(Some(field.byte_size(repr))); ty.set_align(field.align(repr)); FieldLayout { diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 8a54b25..3123d01 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -457,13 +457,52 @@ fn external_vec_type() { } } +#[test] +fn test_set_align_size() { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: u32x1, + c: sm::Array>, + } + + let mut layouts = [ + gpu_layout::(), + gpu_layout::(), + gpu_layout::(), + gpu_layout::(), + gpu_layout::>(), + gpu_layout::>(), + ]; + + for (i, lay) in layouts.iter_mut().enumerate() { + let new_align = sm::U32PowerOf2::_128; + assert_ne!(lay.align(), new_align, "#{i}: align of {lay} is already {new_align:?}, change new_align to make the test work"); + lay.set_align(new_align); + assert_eq!(lay.align(), new_align, "#{i}: align of {lay} is not {new_align:?}"); + + let new_size = 128; + assert_ne!(lay.byte_size(), Some(new_size), "#{i}: size of {lay} is already {new_size}, change new_size to make the test work"); + match lay.removable_byte_size_mut() { + Ok(removable) => *removable = Some(new_size), + Err(fixed) => *fixed = new_size, + }; + assert_eq!(lay.byte_size(), Some(new_size), "#{i}: size of {lay} is not {new_size:?}"); + + if let Ok(removable) = lay.removable_byte_size_mut() { + *removable = None; + assert_eq!(lay.byte_size(), None, "#{i}: size of {lay} is not None"); + }; + } +} + /// helper for defining a `TypeLayout` for a cpu type that /// represents a `GpuSemantics` on the Gpu, but has alignment and size of `Layout` on the Cpu pub fn rust_layout_with_shame_semantics() -> sm::TypeLayout { let mut layout = sm::gpu_layout::(); layout.set_align(CpuType::CPU_ALIGNMENT); - layout.set_byte_size(Some(size_of::() as u64)); + layout.set_byte_size_if_some(Some(size_of::() as u64)); // these are just here because we are testing assert_eq!(layout.align().as_u32(), align_of::() as u32); From 8d9c9de96035ac81c8ae5704ee3d2dce93cbf9dd Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sun, 19 Oct 2025 12:33:47 +0200 Subject: [PATCH 159/182] changes to `TypeLayout`'s size/align mutation api part 1 --- .../frontend/rust_types/type_layout/mod.rs | 31 ++++++++++--------- .../type_layout/recipe/to_layout.rs | 4 +-- shame/tests/test_layout.rs | 11 ++++--- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 7726e4e..7382fbe 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -9,15 +9,8 @@ use std::{ use crate::{ any::U32PowerOf2, call_info, - common::{ - ignore_eq::IgnoreInEqOrdHash, - prettify::{set_color}, - }, - ir::{ - self, - ir_type::{CanonName}, - recording::Context, - }, + common::{ignore_eq::IgnoreInEqOrdHash, prettify::set_color}, + ir::{self, ir_type::CanonName, recording::Context}, }; use recipe::{Matrix, Vector, PackedVector}; @@ -176,7 +169,7 @@ impl TypeLayout { } } -/// mutable reference to the alignment requirement of the represented type. + /// mutable reference to the alignment requirement of the represented type. /// /// may allocate a new `Rc` (via `Rc::make_mut`) for `Array`/`Struct` layouts pub fn align_mut(&mut self) -> &mut U32PowerOf2 { @@ -197,6 +190,16 @@ impl TypeLayout { } } + /// set the byte size of the `TypeLayout` to `new_size` (or `Some(new_size)` if the type can be unsized) + /// + /// use [`removable_byte_size_mut`] if you need more control + pub fn set_byte_size(&mut self, new_size: u64) { + match self.removable_byte_size_mut() { + Ok(removable) => *removable = Some(new_size), + Err(fixed) => *fixed = new_size, + } + } + /// mutable access to the size of a `self` /// /// returns @@ -233,12 +236,12 @@ impl TypeLayout { } TypeLayout::Array(a) => { let mut array = (**a).clone(); -// FIXME: confusing: this is always written, even if self is sized and `byte_size` is None + // FIXME: confusing: this is always written, even if self is sized and `byte_size` is None array.byte_size = byte_size; *a = Rc::new(array); } TypeLayout::Struct(s) => { -// FIXME: confusing: this is always written, even if self is sized and `byte_size` is None + // FIXME: confusing: this is always written, even if self is sized and `byte_size` is None let mut struct_ = (**s).clone(); struct_.byte_size = byte_size; *s = Rc::new(struct_); @@ -247,9 +250,7 @@ impl TypeLayout { } /// Sets the alignment - pub fn set_align(&mut self, align: U32PowerOf2) { - *self.align_mut() = align - } + pub fn set_align(&mut self, align: U32PowerOf2) { *self.align_mut() = align } // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { diff --git a/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs index bd89aa4..2cac7ee 100644 --- a/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/to_layout.rs @@ -187,8 +187,8 @@ fn sized_field_to_field_layout(field: &SizedField, offset: u64, repr: Repr) -> F let mut ty = field.ty.layout(repr); // VERY IMPORTANT: TypeLayout::from_sized_type does not take into account // custom_min_align and custom_min_size, but field.byte_size and field.align do. - ty.set_byte_size_if_some(Some(field.byte_size(repr))); - ty.set_align(field.align(repr)); + ty.set_byte_size(field.byte_size(repr)); + *ty.align_mut() = field.align(repr); FieldLayout { rel_byte_offset: offset, diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 3123d01..22b5844 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -152,7 +152,7 @@ impl CpuLayout for f32x3_cpu { // TODO(release): replace this with `rust_layout_with_shame_semantics::()` // and find a proper solution to the consequences. Its size is 16, and not 12. let mut layout = gpu_layout::(); - layout.set_align(Self::CPU_ALIGNMENT); + *layout.align_mut() = Self::CPU_ALIGNMENT; layout } } @@ -193,7 +193,7 @@ impl CpuLayout for f32x3_align4 { // TODO(release): replace this with `rust_layout_with_shame_semantics::()` // and find a proper solution to the consequences. Its size is 16, and not 12. let mut layout = gpu_layout::(); - layout.set_align(Self::CPU_ALIGNMENT); + *layout.align_mut() = Self::CPU_ALIGNMENT; layout } } @@ -207,7 +207,7 @@ impl CpuLayout for f32x3_size32 { // TODO(release): replace this with `rust_layout_with_shame_semantics::()` // and find a proper solution to the consequences. Its size is 16, and not 12. let mut layout = gpu_layout::(); - layout.set_align(Self::CPU_ALIGNMENT); + *layout.align_mut() = Self::CPU_ALIGNMENT; layout } } @@ -457,6 +457,7 @@ fn external_vec_type() { } } +#[rustfmt::skip] #[test] fn test_set_align_size() { #[derive(sm::GpuLayout)] @@ -478,7 +479,7 @@ fn test_set_align_size() { for (i, lay) in layouts.iter_mut().enumerate() { let new_align = sm::U32PowerOf2::_128; assert_ne!(lay.align(), new_align, "#{i}: align of {lay} is already {new_align:?}, change new_align to make the test work"); - lay.set_align(new_align); + *lay.align_mut() = new_align; assert_eq!(lay.align(), new_align, "#{i}: align of {lay} is not {new_align:?}"); let new_size = 128; @@ -501,7 +502,7 @@ fn test_set_align_size() { pub fn rust_layout_with_shame_semantics() -> sm::TypeLayout { let mut layout = sm::gpu_layout::(); - layout.set_align(CpuType::CPU_ALIGNMENT); + *layout.align_mut() = CpuType::CPU_ALIGNMENT; layout.set_byte_size_if_some(Some(size_of::() as u64)); // these are just here because we are testing From 6968f7ed72e648887c41c79b195b3cbc1812905f Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sun, 19 Oct 2025 15:36:10 +0200 Subject: [PATCH 160/182] changes to `TypeLayout`'s size/align mutation api part 2 --- shame/src/common/proc_macro_utils.rs | 132 +++++++++++++++--- .../src/frontend/rust_types/type_layout/eq.rs | 4 +- .../frontend/rust_types/type_layout/mod.rs | 37 ----- shame/src/ir/ir_type/layout_constraints.rs | 7 +- shame/tests/test_layout.rs | 2 +- shame_derive/src/derive_layout.rs | 4 +- 6 files changed, 120 insertions(+), 66 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 320fe1a..495ace2 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -1,6 +1,7 @@ use std::rc::Rc; use crate::{ + call_info, frontend::{ any::{Any, InvalidReason}, rust_types::{ @@ -65,12 +66,76 @@ pub enum ReprCError { SecondLastElementIsUnsized, } +/// created when a cpu layout is observed to not match the values of `std::mem::size_of`, `std::mem::align_of`, `CpuAligned::CPU_SIZE`, and `CpuAligned::CPU_ALIGNMENT` +#[derive(Debug, thiserror::Error, Clone)] +pub enum CpuLayoutImplMismatch { + UnexpectedSize { + type_name: String, + struct_name: &'static str, + /// size that was expected by `std::mem::size_of` / `CpuAligned::CPU_SIZE` + expected: Option, + /// size that was provided by the (maybe user defined) impl of `CpuLayout` + cpu_layout_provided: Option, + }, +} + +impl std::fmt::Display for CpuLayoutImplMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CpuLayoutImplMismatch::UnexpectedSize { + type_name: t, + struct_name, + expected: std_mem_size, + cpu_layout_provided: cpu_layout_impl_size, + } => { + write!(f, "The `CpuLayout` implementation of `{t}` claims that `{t}` ")?; + match cpu_layout_impl_size { + Some(s) => write!(f, "has a compile-time known size of {s},")?, + None => write!(f, "is unsized at compile-time,")?, + }; + write!(f, "\nbut a `{t}` within `{struct_name}` was just observed ")?; + match std_mem_size { + Some(s) => write!(f, "having a compile-time known size of {s}")?, + None => write!(f, "being unsized")?, + }; + writeln!(f, ".")?; + writeln!( + f, + "This is most likely caused by a mistake in the `shame::CpuLayout` implementation of {t} + or in the implementation of one of the types it is composed of. + The size must be equal to what `std::mem::size_of` returns." + )?; + } + } + Ok(()) + } +} + +#[track_caller] +fn try_report_cpu_layout_impl_mismatch(err: CpuLayoutImplMismatch) { + let caller = call_info!(); + let success = Context::try_with(caller, |ctx| { + ctx.push_error(crate::frontend::encoding::EncodingErrorKind::LayoutError( + err.clone().into(), + )); + }) + .unwrap_or_else(|| { + if crate::__private::DEBUG_PRINT_ENABLED { + println!("`shame` warning @ {caller}:\n{err}"); + } else { + // unable to report assumed implementation mistake of `CpuLayout` for a given type + } + }); +} + +#[track_caller] pub fn repr_c_struct_layout( repr_c_align_attribute: Option, struct_name: &'static str, first_fields_with_offsets_and_sizes: &[(ReprCField, usize, usize)], mut last_field: ReprCField, - last_field_size: Option, + // the size of the last field according to the `CpuAligned` trait's associated constant + last_field_trait_size: Option, ) -> Result { let last_field_offset = match first_fields_with_offsets_and_sizes.last() { None => 0, @@ -85,41 +150,62 @@ pub fn repr_c_struct_layout( } }; - let max_alignment = first_fields_with_offsets_and_sizes - .iter() - .map(|(f, _, _)| f.alignment) - .fold(last_field.alignment, ::std::cmp::max); - - let struct_alignment = match repr_c_align_attribute { - Some(repr_c_align) => max_alignment.max(repr_c_align), - None => max_alignment, + let struct_alignment = { + let max_alignment = first_fields_with_offsets_and_sizes + .iter() + .map(|(f, _, _)| f.alignment) + .fold(last_field.alignment, ::std::cmp::max); + match repr_c_align_attribute { + Some(repr_c_align_attribute) => max_alignment.max(repr_c_align_attribute), + None => max_alignment, + } }; - let last_field_size = last_field_size.map(|s| s as u64); + + /// the size of the last field according to the `CpuAligned` trait's associated constant + let last_field_trait_size = last_field_trait_size.map(|s| s as u64); let total_struct_size = - last_field_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); + last_field_trait_size.map(|last_size| round_up(struct_alignment.as_u64(), last_field_offset + last_size)); - let new_size = |layout_size: Option, actual_size: Option| match (layout_size, actual_size) { - (_, Some(s)) => Some(s), // prefer actual size - (Some(s), None) => Some(s), // but still use layout size if no actual size - (None, None) => None, - }; let mut fields = first_fields_with_offsets_and_sizes .iter() - .map(|(field, offset, size)| (field, *offset as u64, *size as u64)) - .map(|(mut field, offset, size)| { + .map(|(field, std_mem_offset_of, std_mem_size_of)| (field, *std_mem_offset_of as u64, *std_mem_size_of as u64)) + .map(|(mut field, std_mem_offset_of, std_mem_size_of)| { let mut layout = field.layout.clone(); - layout.set_byte_size_if_some(new_size(field.layout.byte_size(), Some(size))); + // here `std::mem::size_of` is prioritized over `<#field_type>::cpu_layout().byte_size()`. + // They can disagree if the user-driven `cpu_layout()` implementation is broken. TODO(release) reconsider this, especially in the case of f32x3 + layout.set_byte_size(std_mem_size_of); FieldLayout { - rel_byte_offset: offset, + rel_byte_offset: std_mem_offset_of, name: field.name.into(), ty: layout, } }) .chain(std::iter::once({ - last_field - .layout - .set_byte_size_if_some(new_size(last_field.layout.byte_size(), last_field_size)); + if last_field.layout.byte_size() != last_field_trait_size { + try_report_cpu_layout_impl_mismatch(CpuLayoutImplMismatch::UnexpectedSize { + struct_name, + type_name: last_field.layout.short_name(), + expected: last_field_trait_size, + cpu_layout_provided: last_field.layout.byte_size(), + }); + } + + // here `<#last_field_type as CpuAligned>::CPU_SIZE` is prioritized over `<#field_type>::cpu_layout().byte_size()`. + // if the reporting above failed. The two can disagree if the user-driven `cpu_layout()` implementation is + // broken. + // + // If the error could not be reported above, the user cannot be informed and they + // have to accept the consequences of their broken `CpuLayout` impl. + match (last_field.layout.removable_byte_size_mut(), last_field_trait_size) { + (Ok(layout_maybe_size), trait_maybe_size) => *layout_maybe_size = trait_maybe_size, + (Err(layout_size), Some(trait_size)) => *layout_size = trait_size, + (Err(layout_size), None) => { + // in this case the rust type is an always-sized type like vector/matrix/packedvec, + // but the `CpuLayout` impl claims it is an unsized struct or array. + } + }; + FieldLayout { rel_byte_offset: last_field_offset, name: last_field.name.into(), diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index d16c62f..a97a74a 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -559,7 +559,7 @@ mod tests { impl CpuLayout for f32x3_align4 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_align(shame::any::U32PowerOf2::_4); + *layout.align_mut() = shame::any::U32PowerOf2::_4; layout } } @@ -572,7 +572,7 @@ mod tests { impl CpuLayout for f32x3_size16 { fn cpu_layout() -> shame::TypeLayout { let mut layout = gpu_layout::(); - layout.set_byte_size_if_some(Some(16)); + layout.set_byte_size(size_of::() as u64); layout } } diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index 7382fbe..b16bfc1 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -215,43 +215,6 @@ impl TypeLayout { } } - // TODO: remove, this function does not always behave as specified (struct being sized, `byte_size` = None) - /// If self is sized and `byte_size` is None, the size is not overwritten. - pub fn set_byte_size_if_some(&mut self, byte_size: Option) { - match self { - TypeLayout::Vector(v) => { - if let Some(size) = byte_size { - v.byte_size = size; - } - } - TypeLayout::Matrix(m) => { - if let Some(size) = byte_size { - m.byte_size = size; - } - } - TypeLayout::PackedVector(v) => { - if let Some(size) = byte_size { - v.byte_size = size; - } - } - TypeLayout::Array(a) => { - let mut array = (**a).clone(); - // FIXME: confusing: this is always written, even if self is sized and `byte_size` is None - array.byte_size = byte_size; - *a = Rc::new(array); - } - TypeLayout::Struct(s) => { - // FIXME: confusing: this is always written, even if self is sized and `byte_size` is None - let mut struct_ = (**s).clone(); - struct_.byte_size = byte_size; - *s = Rc::new(struct_); - } - } - } - - /// Sets the alignment - pub fn set_align(&mut self, align: U32PowerOf2) { *self.align_mut() = align } - // TODO(chronicl) this should be removed with improved any api for storage/uniform bindings pub(crate) fn from_store_ty(store_type: ir::StoreType) -> Result { let t: recipe::TypeLayoutRecipe = store_type.try_into()?; diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index 3202501..c28028b 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -6,7 +6,10 @@ use std::{ use thiserror::Error; -use crate::frontend::rust_types::type_layout::{display::LayoutInfo, eq::CheckEqLayoutMismatch, TypeLayout}; +use crate::{ + common::proc_macro_utils::CpuLayoutImplMismatch, + frontend::rust_types::type_layout::{display::LayoutInfo, eq::CheckEqLayoutMismatch, TypeLayout}, +}; use crate::{ backend::language::Language, call_info, @@ -480,6 +483,8 @@ Type `{}` contains type `{struct_or_block_name}` which has a custom byte-alignme gpu_name: String, gpu_stride: u64, }, + #[error(transparent)] + CpuLayoutImplMismatch(#[from] CpuLayoutImplMismatch), } #[allow(missing_docs)] diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index 22b5844..d77952f 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -503,7 +503,7 @@ pub fn rust_layout_with_shame_semantics() let mut layout = sm::gpu_layout::(); *layout.align_mut() = CpuType::CPU_ALIGNMENT; - layout.set_byte_size_if_some(Some(size_of::() as u64)); + layout.set_byte_size(size_of::() as u64); // these are just here because we are testing assert_eq!(layout.align().as_u32(), align_of::() as u32); diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 5f4b35a..1fb8a35 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -242,7 +242,7 @@ pub fn impl_for_struct( // GpuType is not implemented for derived structs directly, so they can't be used // as the field of another struct, instead shame::Struct has to be used, which // only accepts sized structs. - Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), + Err(#re::StructFromPartsError::MustNotHaveUnsizedStructField) => unreachable!("GpuType bound for fields makes this impossible"), } } @@ -600,7 +600,7 @@ pub fn impl_for_struct( layout: <#first_fields_type>::cpu_layout(), // DO NOT refactor to `as #re::CpuLayout`, that would prevent the duck-trait trick for circumventing the orphan rule }, std::mem::offset_of!(#derive_struct_ident, #first_fields_ident), - std::mem::size_of::<#first_fields_type>(), + std::mem::size_of::<#first_fields_type>(), // TODO(release): it is correct that this uses `std::mem::size_of`. At the time of writing, the example implementations of `CpuLayout` for `f32x3_cpu` in the type layout tests are technically wrong, causing the size of <#first_fields_type>::cpu_layout() to disagree with this one. This needs to be addressed! )),* ], #re::ReprCField { From 54406096b4f6d171b295a931a5c13767f86dce7d Mon Sep 17 00:00:00 2001 From: RayMarch Date: Sun, 19 Oct 2025 15:46:40 +0200 Subject: [PATCH 161/182] added panic that we need to resolve later --- shame/src/common/proc_macro_utils.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 495ace2..1799dcf 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -101,9 +101,9 @@ impl std::fmt::Display for CpuLayoutImplMismatch { writeln!(f, ".")?; writeln!( f, - "This is most likely caused by a mistake in the `shame::CpuLayout` implementation of {t} - or in the implementation of one of the types it is composed of. - The size must be equal to what `std::mem::size_of` returns." + "This is most likely caused by a mistake in the `shame::CpuLayout` implementation of {t} \ + or in the implementation of one of the types it is composed of. \ + The size must be equal to what `std::mem::size_of` returns." )?; } } @@ -124,6 +124,7 @@ fn try_report_cpu_layout_impl_mismatch(err: CpuLayoutImplMismatch) { println!("`shame` warning @ {caller}:\n{err}"); } else { // unable to report assumed implementation mistake of `CpuLayout` for a given type + panic!("shame error at {caller} \n{err}"); } }); } From 7ff81fc06211659285d03a3f6f53dfe1381cb874 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 5 Jan 2026 10:06:29 +0100 Subject: [PATCH 162/182] replace `unreachable!()` in error display --- .../rust_types/type_layout/compatible_with.rs | 1441 +++++++++-------- 1 file changed, 721 insertions(+), 720 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 7f121e1..9653518 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -1,720 +1,721 @@ -use std::fmt::Write; - -use crate::{ - any::layout::StructLayout, - call_info, - common::prettify::{set_color, UnwrapOrStr}, - frontend::{ - encoding::buffer::BufferAddressSpaceEnum, - rust_types::type_layout::{ - display::LayoutInfo, - eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, - recipe::to_layout::RecipeContains, - ArrayLayout, - }, - }, - ir::{ir_type::max_u64_po2_dividing, recording::Context}, - mem, BufferAddressSpace, Language, TypeLayout, -}; - -use super::{recipe::TypeLayoutRecipe, Repr}; - -pub use mem::{Storage, Uniform}; - -/// `TypeLayoutCompatibleWith` is a [`TypeLayoutRecipe`] with the additional -/// guarantee that the [`TypeLayout`] it produces is compatible with the specified `AddressSpace`. -/// -/// Address space requirements are language specific, which is why `TypeLayoutCompatibleWith` constructors -/// additionally take a [`Language`] parameter. -/// -/// To be "compatible with" an address space means that -/// - the recipe is **valid** ([`TypeLayoutRecipe::layout`] succeeds) -/// - the type layout **satisfies the layout requirements** of the address space -/// - the type layout recipe is **representable** in the target language -/// -/// To be representable in a language means that the type layout recipe, can be expressed in the -/// language's type system: -/// 1. all types in the recipe can be expressed in the target language (for example `bool` or `PackedVector` can't be expressed in wgsl) -/// 2. the available layout algorithms in the target language can produce the same layout as the one produced by the recipe -/// 3. support for the custom attributes the recipe uses, such as `#[align(N)]` and `#[size(N)]`. -/// Custom attributes may be rejected by the target language itself (NotRepresentable error) -/// or by the layout algorithms specified in the recipe (InvalidRecipe error during `TypeLayoutRecipe -> TypeLayout` conversion). -/// -/// For example for wgsl we have -/// 1. PackedVector can be part of a recipe, but can not be expressed in wgsl, -/// so a recipe containing a PackedVector is not representable in wgsl. -/// 2. Wgsl has only one layout algorithm (`Repr::Wgsl`) - there is no choice between std140 and std430 -/// like in glsl - so to be representable in wgsl the type layout produced by the recipe -/// has to be the same as the one produced by the same recipe but using exclusively the Repr::Wgsl -/// layout algorithm instead of the layout algorithms specified in the recipe. -/// 3. Wgsl only supports custom struct field attributes `#[align(N)]` and `#[size(N)]` currently. -#[derive(Debug, Clone)] -pub struct TypeLayoutCompatibleWith { - recipe: TypeLayoutRecipe, - _phantom: std::marker::PhantomData, -} - -impl TypeLayoutCompatibleWith { - pub fn try_from(language: Language, recipe: TypeLayoutRecipe) -> Result { - let address_space = AS::BUFFER_ADDRESS_SPACE; - let layout = recipe.layout(); - - match (language, address_space, layout.byte_size()) { - // Must be sized in wgsl's uniform address space - (Language::Wgsl, BufferAddressSpaceEnum::Uniform, None) => { - return Err(RequirementsNotSatisfied::MustBeSized(recipe, language, address_space).into()); - } - (Language::Wgsl, BufferAddressSpaceEnum::Uniform, Some(_)) - | (Language::Wgsl, BufferAddressSpaceEnum::Storage, _) => {} - } - - // Check that the recipe is representable in the target language. - // See `TypeLayoutCompatibleWith` docs for more details on what it means to be representable. - match (language, address_space) { - (Language::Wgsl, BufferAddressSpaceEnum::Storage | BufferAddressSpaceEnum::Uniform) => { - // We match like this, so that future additions to `RecipeContains` lead us here. - match RecipeContains::CustomFieldAlign { - // supported in wgsl - RecipeContains::CustomFieldAlign | RecipeContains::CustomFieldSize | - // not supported in wgsl - RecipeContains::PackedVector => { - if recipe.contains(RecipeContains::PackedVector) { - return Err(NotRepresentable::MayNotContain( - recipe, - language, - address_space, - RecipeContains::PackedVector, - ) - .into()); - } - } - } - - // Wgsl has only one layout algorithm - let recipe_wgsl = recipe.to_unified_repr(Repr::Wgsl); - let layout_wgsl = recipe_wgsl.layout_with_default_repr(Repr::Wgsl); - if layout != layout_wgsl { - match try_find_mismatch(&layout, &layout_wgsl) { - Some(mismatch) => { - return Err(NotRepresentable::LayoutError(LayoutError { - recipe, - kind: LayoutErrorKind::NotRepresentable, - language, - address_space, - mismatch, - colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(false), - }) - .into()); - } - None => return Err(NotRepresentable::UnknownLayoutError(recipe, address_space).into()), - } - } - } - } - - // Check that the type layout satisfies the requirements of the address space - match (language, address_space) { - (Language::Wgsl, BufferAddressSpaceEnum::Storage) => { - // As long as the recipe is representable in wgsl, it satifies the storage address space requirements. - // We already checked that the recipe is representable in wgsl above. - } - (Language::Wgsl, BufferAddressSpaceEnum::Uniform) => { - // Repr::WgslUniform is made for exactly this purpose: to check that the type layout - // satisfies the requirements of wgsl's uniform address space. - let recipe_unified = recipe.to_unified_repr(Repr::WgslUniform); - let layout_unified = recipe_unified.layout(); - if layout != layout_unified { - match try_find_mismatch(&layout, &layout_unified) { - Some(mismatch) => { - return Err(RequirementsNotSatisfied::LayoutError(LayoutError { - recipe, - kind: LayoutErrorKind::RequirementsNotSatisfied, - language, - address_space, - mismatch, - colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) - .unwrap_or(false), - }) - .into()); - } - None => return Err(RequirementsNotSatisfied::UnknownLayoutError(recipe, address_space).into()), - } - } - } - } - - Ok(Self { - recipe, - _phantom: std::marker::PhantomData, - }) - } -} - -#[derive(thiserror::Error, Debug, Clone)] -pub enum AddressSpaceError { - #[error("{0}")] - NotRepresentable(#[from] NotRepresentable), - #[error("{0}")] - RequirementsNotSatisfied(#[from] RequirementsNotSatisfied), -} - -#[derive(thiserror::Error, Debug, Clone)] -pub enum NotRepresentable { - #[error("{0}")] - LayoutError(LayoutError), - #[error("{0} contains a {3}, which is not allowed in {1}'s {2}.")] - MayNotContain(TypeLayoutRecipe, Language, BufferAddressSpaceEnum, RecipeContains), - #[error("Unknown layout error occured for {0} in {1}.")] - UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), -} - -#[derive(thiserror::Error, Debug, Clone)] -pub enum RequirementsNotSatisfied { - #[error("{0}")] - LayoutError(LayoutError), - #[error( - "The size of `{0}` on the gpu is not known at compile time. {1}'s {2} \ - requires that the size of {0} on the gpu is known at compile time." - )] - MustBeSized(TypeLayoutRecipe, Language, BufferAddressSpaceEnum), - #[error("Unknown layout error occured for {0} in {1}.")] - UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), -} - -#[derive(Debug, Clone)] -pub struct LayoutError { - recipe: TypeLayoutRecipe, - mismatch: LayoutMismatch, - - /// Used to adjust the error message - /// to fit `NotRepresentable` or `RequirementsNotSatisfied`. - kind: LayoutErrorKind, - language: Language, - address_space: BufferAddressSpaceEnum, - - colored: bool, -} - -#[derive(Debug, Clone, Copy)] -pub enum LayoutErrorKind { - NotRepresentable, - RequirementsNotSatisfied, -} - -impl LayoutError { - /// Returns the context the error occurred in. The "{language}" in case of a `NotRepresentable` error, - /// or the "{language}'s {address_space}" in case of a `RequirementsNotSatisfied` error. - fn context(&self) -> &'static str { - match self.kind { - LayoutErrorKind::NotRepresentable => match self.language { - Language::Wgsl => "wgsl", - }, - LayoutErrorKind::RequirementsNotSatisfied => match (self.language, self.address_space) { - (Language::Wgsl, BufferAddressSpaceEnum::Storage) => "wgsl's storage address space", - (Language::Wgsl, BufferAddressSpaceEnum::Uniform) => "wgsl's uniform address space", - }, - } - } -} - -impl std::error::Error for LayoutError {} -impl std::fmt::Display for LayoutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.kind { - LayoutErrorKind::NotRepresentable => { - writeln!(f, "`{}` is not representable in {}:", self.recipe, self.language)?; - } - LayoutErrorKind::RequirementsNotSatisfied => { - writeln!( - f, - "`{}` does not satisfy the layout requirements of {}:", - self.recipe, - self.context() - )?; - } - } - - match &self.mismatch { - LayoutMismatch::TopLevel { - layout_left, - layout_right, - mismatch, - } => write_top_level_mismatch(f, self, layout_left, layout_right, mismatch), - LayoutMismatch::Struct { - struct_left, - struct_right, - mismatch, - } => write_struct_mismatch(f, self, struct_left, struct_right, mismatch), - } - } -} - -fn write_top_level_mismatch( - f: &mut std::fmt::Formatter<'_>, - error: &LayoutError, - layout_left: &TypeLayout, - layout_right: &TypeLayout, - mismatch: &TopLevelMismatch, -) -> Result<(), std::fmt::Error> { - match mismatch { - TopLevelMismatch::Type => unreachable!( - "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same" - ), - TopLevelMismatch::ArrayStride { - array_left, - array_right, - } => { - let outer_most_array_has_mismatch = array_left.short_name() == layout_left.short_name(); - if outer_most_array_has_mismatch { - writeln!( - f, - "`{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - array_right.byte_stride, - error.context(), - array_left.byte_stride - )?; - } else { - writeln!( - f, - "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - layout_left.short_name(), - array_right.byte_stride, - error.context(), - array_left.byte_stride - )?; - } - } - TopLevelMismatch::ByteSize { left, right } => { - let outer_most_array_has_mismatch = left.short_name() == layout_left.short_name(); - if outer_most_array_has_mismatch { - writeln!( - f, - "`{}` has a byte size of {} in {}, but has a byte size of {}.", - layout_left.short_name(), - UnwrapOrStr(layout_left.byte_size(), ""), - error.context(), - UnwrapOrStr(layout_right.byte_size(), "") - )?; - } else { - writeln!( - f, - "`{}` in `{}` has a byte size of {} in {}, but has a byte size of {}.", - layout_left.short_name(), - layout_left.short_name(), - UnwrapOrStr(layout_left.byte_size(), ""), - error.context(), - UnwrapOrStr(layout_right.byte_size(), "") - )?; - } - } - } - Ok(()) -} - -fn write_struct_mismatch( - f: &mut std::fmt::Formatter<'_>, - error: &LayoutError, - struct_left: &StructLayout, - struct_right: &StructLayout, - mismatch: &StructMismatch, -) -> Result<(), std::fmt::Error> { - match mismatch { - StructMismatch::FieldLayout { - field_index, - field_left, - mismatch: - TopLevelMismatch::ArrayStride { - array_left, - array_right, - }, - .. - } => { - let outer_most_array_has_mismatch = field_left.ty.short_name() == array_left.short_name(); - let layout_info = if outer_most_array_has_mismatch { - LayoutInfo::STRIDE - } else { - // if an inner array has the stride mismatch, showing the outer array's stride could be confusing - LayoutInfo::NONE - }; - - writeln!( - f, - "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", - array_left.short_name(), - struct_left.short_name(), - array_right.byte_stride, - error.context(), - array_left.byte_stride - )?; - writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; - write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; - } - StructMismatch::FieldLayout { - field_index, - field_left, - field_right, - mismatch: TopLevelMismatch::ByteSize { left, right }, - } => { - let outer_most_array_has_mismatch = field_left.ty.short_name() == left.short_name(); - let layout_info = if outer_most_array_has_mismatch { - LayoutInfo::SIZE - } else { - // if an inner array has the byte size mismatch, showing the outer array's byte size could be confusing - LayoutInfo::NONE - }; - - if !outer_most_array_has_mismatch { - write!(f, "`{}` in field", left.short_name())?; - } else { - write!(f, "Field")?; - } - writeln!( - f, - " `{}` of `{}` requires a byte size of {} in {}, but has a byte size of {}", - field_left.name, - struct_left.name, - UnwrapOrStr(field_right.ty.byte_size(), ""), - error.context(), - UnwrapOrStr(field_left.ty.byte_size(), "") - )?; - writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; - write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; - } - StructMismatch::FieldOffset { - field_index, - field_left, - field_right, - } => { - let field_name = &field_left.name; - let offset = field_left.rel_byte_offset; - let expected_align = field_right.ty.align().as_u64(); - let actual_align = max_u64_po2_dividing(field_left.rel_byte_offset); - - writeln!( - f, - "Field `{}` of `{}` needs to be {} byte aligned in {}, but has a byte-offset of {}, which is only {} byte aligned", - field_name, - struct_left.name, - expected_align, - error.context(), - offset, - actual_align - )?; - - writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; - write_struct( - f, - struct_left, - LayoutInfo::OFFSET | LayoutInfo::ALIGN | LayoutInfo::SIZE, - Some(*field_index), - error.colored, - )?; - - writeln!(f, "\nPotential solutions include:")?; - writeln!( - f, - "- add an #[align({})] attribute to the definition of `{}`", - field_right.ty.align().as_u32(), - field_name - )?; - writeln!( - f, - "- increase the offset of `{field_name}` until it is divisible by {expected_align} by making previous fields larger or adding fields before it" - )?; - match (error.kind, error.language, error.address_space) { - (LayoutErrorKind::RequirementsNotSatisfied, Language::Wgsl, BufferAddressSpaceEnum::Uniform) => { - writeln!(f, "- use a storage binding instead of a uniform binding")?; - } - ( - LayoutErrorKind::NotRepresentable | LayoutErrorKind::RequirementsNotSatisfied, - Language::Wgsl, - BufferAddressSpaceEnum::Storage | BufferAddressSpaceEnum::Uniform, - ) => {} - } - writeln!(f)?; - - match error.language { - Language::Wgsl => { - match error.address_space { - BufferAddressSpaceEnum::Uniform => writeln!( - f, - "In {}, structs, arrays and array elements must be at least 16 byte aligned.", - error.context() - )?, - BufferAddressSpaceEnum::Storage => {} - } - - match (error.kind, error.address_space) { - ( - LayoutErrorKind::RequirementsNotSatisfied, - BufferAddressSpaceEnum::Uniform | BufferAddressSpaceEnum::Storage, - ) => writeln!( - f, - "More info about the wgsl's {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", - error.address_space - )?, - (LayoutErrorKind::NotRepresentable, _) => writeln!( - f, - "More info about the wgsl's layout algorithm can be found at https://www.w3.org/TR/WGSL/#alignment-and-size" - )?, - } - } - } - } - StructMismatch::FieldCount - | StructMismatch::FieldName { .. } - | StructMismatch::FieldLayout { - mismatch: TopLevelMismatch::Type, - .. - } => { - unreachable!( - "The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same" - ) - } - } - Ok(()) -} - -fn write_struct( - f: &mut W, - s: &StructLayout, - layout_info: LayoutInfo, - highlight_field: Option, - colored: bool, -) -> std::fmt::Result -where - W: Write, -{ - let use_256_color_mode = false; - - let mut writer = s.writer(layout_info); - writer.writeln_header(f); - writer.writeln_struct_declaration(f); - for field_index in 0..s.fields.len() { - if Some(field_index) == highlight_field { - if colored { - set_color(f, Some("#508EE3"), use_256_color_mode)?; - } - writer.write_field(f, field_index)?; - writeln!(f, " <--")?; - if colored { - set_color(f, None, use_256_color_mode)?; - } - } else { - writer.writeln_field(f, field_index); - } - } - writer.writeln_struct_end(f)?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::pipeline_kind::Render; - use crate::{self as shame, EncodingGuard, ThreadIsAlreadyEncoding}; - use shame as sm; - use shame::{aliases::*, GpuLayout}; - - const PRINT: bool = false; - - macro_rules! is_struct_mismatch { - ($result:expr, $as_error:ident, $mismatch:pat) => {{ - if let Err(e) = &$result - && PRINT - { - println!("{e}"); - } - matches!( - $result, - Err(AddressSpaceError::$as_error($as_error::LayoutError(LayoutError { - mismatch: LayoutMismatch::Struct { - mismatch: $mismatch, - .. - }, - .. - }))) - ) - }}; - } - - fn enable_color() -> Option, ThreadIsAlreadyEncoding>> { - PRINT.then(|| sm::start_encoding(sm::Settings::default())) - } - - #[test] - fn field_offset_error_not_representable() { - let _guard = enable_color(); - - #[derive(sm::GpuLayout)] - #[gpu_repr(packed)] - struct A { - a: f32x1, - // has offset 4, but in wgsl's storage/uniform address space, it needs to be 16 byte aligned - b: f32x3, - } - - // The error variant is NotRepresentable, because there is no way to represent it in wgsl, - // because an offset of 4 is not possible for f32x3, because needs to be 16 byte aligned. - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), - NotRepresentable, - StructMismatch::FieldOffset { .. } - )); - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), - NotRepresentable, - StructMismatch::FieldOffset { .. } - )); - } - - #[test] - fn wgsl_uniform_array_stride_requirements_not_satisfied() { - let _guard = enable_color(); - - #[derive(sm::GpuLayout)] - struct A { - a: f32x1, - // has stride 4, but wgsl's uniform address space requires a stride of 16. - // also, has wrong offset, because array align is multiple of 16 in wgsl's uniform address space. - // array stride error has higher priority than field offset error, - b: sm::Array>, - } - - // The error variant is RequirementsNotSatisfied, because the array has a stride of 4 in Repr::Packed, - // but wgsl's uniform address space requires a stride of 16. - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), - RequirementsNotSatisfied, - StructMismatch::FieldLayout { - mismatch: TopLevelMismatch::ArrayStride { .. }, - .. - } - )); - - // Testing that the error remains the same when nested in another struct - #[derive(sm::GpuLayout)] - struct B { - a: sm::Struct, - } - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(Language::Wgsl, B::layout_recipe()), - RequirementsNotSatisfied, - StructMismatch::FieldLayout { - mismatch: TopLevelMismatch::ArrayStride { .. }, - .. - } - )); - - // Testing that the error remains the same when nested in an array - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from( - Language::Wgsl, - , sm::Size<1>>>::layout_recipe() - ), - RequirementsNotSatisfied, - StructMismatch::FieldLayout { - mismatch: TopLevelMismatch::ArrayStride { .. }, - .. - } - )); - } - - #[test] - fn wgsl_uniform_field_offset_requirements_not_satisfied() { - let _guard = enable_color(); - - #[derive(sm::GpuLayout)] - struct A { - a: f32x1, - b: sm::Struct, - } - #[derive(sm::GpuLayout)] - struct B { - a: f32x1, - } - - // The error variant is RequirementsNotSatisfied, because the array has a stride of 4 in Repr::Packed, - // but wgsl's uniform address space requires a stride of 16. - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), - RequirementsNotSatisfied, - StructMismatch::FieldOffset { .. } - )); - - // Testing that the error remains the same when nested in another struct - #[derive(sm::GpuLayout)] - struct C { - a: sm::Struct, - } - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from(Language::Wgsl, C::layout_recipe()), - RequirementsNotSatisfied, - StructMismatch::FieldOffset { .. } - )); - - // Testing that the error remains the same when nested in an array - assert!(is_struct_mismatch!( - TypeLayoutCompatibleWith::::try_from( - Language::Wgsl, - , sm::Size<1>>>::layout_recipe() - ), - RequirementsNotSatisfied, - StructMismatch::FieldOffset { .. } - )); - } - - #[test] - fn wgsl_uniform_must_be_sized() { - let _guard = enable_color(); - - #[derive(sm::GpuLayout)] - struct A { - a: sm::Array, - } - - let e = TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).unwrap_err(); - if PRINT { - println!("{e}"); - } - assert!(matches!( - e, - AddressSpaceError::RequirementsNotSatisfied(RequirementsNotSatisfied::MustBeSized( - _, - Language::Wgsl, - BufferAddressSpaceEnum::Uniform - )) - )); - - // Storage address space should allow unsized types - assert!(TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).is_ok()); - } - - #[test] - fn wgsl_storage_may_not_contain_packed_vec() { - let _guard = enable_color(); - - #[derive(sm::GpuLayout)] - #[gpu_repr(packed)] - struct A { - a: sm::packed::snorm16x2, - } - let e = TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).unwrap_err(); - if PRINT { - println!("{e}"); - } - assert!(matches!( - e, - AddressSpaceError::NotRepresentable(NotRepresentable::MayNotContain( - _, - Language::Wgsl, - BufferAddressSpaceEnum::Storage, - RecipeContains::PackedVector - )) - )); - } -} +use std::fmt::Write; + +use crate::{ + any::layout::StructLayout, + call_info, + common::prettify::{set_color, UnwrapOrStr}, + frontend::{ + encoding::buffer::BufferAddressSpaceEnum, + rust_types::type_layout::{ + display::LayoutInfoFlags, + eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, + recipe::to_layout::RecipeContains, + ArrayLayout, + }, + }, + ir::{ir_type::max_u64_po2_dividing, recording::Context}, + mem, BufferAddressSpace, Language, TypeLayout, +}; + +use super::{recipe::TypeLayoutRecipe, Repr}; + +pub use mem::{Storage, Uniform}; + +/// `TypeLayoutCompatibleWith` is a [`TypeLayoutRecipe`] with the additional +/// guarantee that the [`TypeLayout`] it produces is compatible with the specified `AddressSpace`. +/// +/// Address space requirements are language specific, which is why `TypeLayoutCompatibleWith` constructors +/// additionally take a [`Language`] parameter. +/// +/// To be "compatible with" an address space means that +/// - the recipe is **valid** ([`TypeLayoutRecipe::layout`] succeeds) +/// - the type layout **satisfies the layout requirements** of the address space +/// - the type layout recipe is **representable** in the target language +/// +/// To be representable in a language means that the type layout recipe can be expressed in the +/// language's type system: +/// 1. all types in the recipe can be expressed in the target language (for example `bool` (with guaranteed align=1, size=1) +/// or `PackedVector` can't be expressed in wgsl) +/// 2. the available layout algorithms in the target language can produce the same layout as the one produced by the recipe +/// 3. support for the custom attributes the recipe uses, such as `#[align(N)]` and `#[size(N)]`. +/// Custom attributes may be rejected by the target language itself (NotRepresentable error) +/// or by the layout algorithms specified in the recipe (InvalidRecipe error during `TypeLayoutRecipe -> TypeLayout` conversion). +/// +/// For example for wgsl we have +/// 1. PackedVector can be part of a recipe, but can not be expressed in wgsl, +/// so a recipe containing a PackedVector is not representable in wgsl. +/// 2. Wgsl has only one layout algorithm (`Repr::Wgsl`) - there is no choice between std140 and std430 +/// like in glsl - so to be representable in wgsl the type layout produced by the recipe +/// has to be the same as the one produced by the same recipe but using exclusively the Repr::Wgsl +/// layout algorithm instead of the layout algorithms specified in the recipe. +/// 3. Wgsl only supports custom struct field attributes `#[align(N)]` and `#[size(N)]` currently. +#[derive(Debug, Clone)] +pub struct TypeLayoutCompatibleWith { + recipe: TypeLayoutRecipe, + _phantom: std::marker::PhantomData, +} + +impl TypeLayoutCompatibleWith { + pub fn try_from(language: Language, recipe: TypeLayoutRecipe) -> Result { + let address_space = AS::BUFFER_ADDRESS_SPACE; + let layout = recipe.layout(); + + match (language, address_space, layout.byte_size()) { + // Must be sized in wgsl's uniform address space + (Language::Wgsl, BufferAddressSpaceEnum::Uniform, None) => { + return Err(RequirementsNotSatisfied::MustBeSized(recipe, language, address_space).into()); + } + (Language::Wgsl, BufferAddressSpaceEnum::Uniform, Some(_)) | + (Language::Wgsl, BufferAddressSpaceEnum::Storage, _) => {} + } + + // Check that the recipe is representable in the target language. + // See `TypeLayoutCompatibleWith` docs for more details on what it means to be representable. + match (language, address_space) { + (Language::Wgsl, BufferAddressSpaceEnum::Storage | BufferAddressSpaceEnum::Uniform) => { + // We match like this, so that future additions to `RecipeContains` lead us here. + match RecipeContains::CustomFieldAlign { + // supported in wgsl + RecipeContains::CustomFieldAlign | RecipeContains::CustomFieldSize | + // not supported in wgsl + RecipeContains::PackedVector => { + if recipe.contains(RecipeContains::PackedVector) { + return Err(NotRepresentable::MayNotContain( + recipe, + language, + address_space, + RecipeContains::PackedVector, + ) + .into()); + } + } + } + + // Wgsl has only one layout algorithm + let recipe_wgsl = recipe.to_unified_repr(Repr::Wgsl); + let layout_wgsl = recipe_wgsl.layout_with_default_repr(Repr::Wgsl); + if layout != layout_wgsl { + match try_find_mismatch(&layout, &layout_wgsl) { + Some(mismatch) => { + return Err(NotRepresentable::LayoutError(LayoutError { + recipe, + kind: LayoutErrorKind::NotRepresentable, + language, + address_space, + mismatch, + colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(false), + }) + .into()); + } + None => return Err(NotRepresentable::UnknownLayoutError(recipe, address_space).into()), + } + } + } + } + + // Check that the type layout satisfies the requirements of the address space + match (language, address_space) { + (Language::Wgsl, BufferAddressSpaceEnum::Storage) => { + // As long as the recipe is representable in wgsl, it satifies the storage address space requirements. + // We already checked that the recipe is representable in wgsl above. + } + (Language::Wgsl, BufferAddressSpaceEnum::Uniform) => { + // Repr::WgslUniform is made for exactly this purpose: to check that the type layout + // satisfies the requirements of wgsl's uniform address space. + let recipe_unified = recipe.to_unified_repr(Repr::WgslUniform); + let layout_unified = recipe_unified.layout(); + if layout != layout_unified { + match try_find_mismatch(&layout, &layout_unified) { + Some(mismatch) => { + return Err(RequirementsNotSatisfied::LayoutError(LayoutError { + recipe, + kind: LayoutErrorKind::RequirementsNotSatisfied, + language, + address_space, + mismatch, + colored: Context::try_with(call_info!(), |ctx| ctx.settings().colored_error_messages) + .unwrap_or(false), + }) + .into()); + } + None => return Err(RequirementsNotSatisfied::UnknownLayoutError(recipe, address_space).into()), + } + } + } + } + + Ok(Self { + recipe, + _phantom: std::marker::PhantomData, + }) + } +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum AddressSpaceError { + #[error("{0}")] + NotRepresentable(#[from] NotRepresentable), + #[error("{0}")] + RequirementsNotSatisfied(#[from] RequirementsNotSatisfied), +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum NotRepresentable { + #[error("{0}")] + LayoutError(LayoutError), + #[error("{0} contains a {3}, which is not allowed in {1}'s {2}.")] + MayNotContain(TypeLayoutRecipe, Language, BufferAddressSpaceEnum, RecipeContains), + #[error("Unknown layout error occured for {0} in {1}.")] + UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum RequirementsNotSatisfied { + #[error("{0}")] + LayoutError(LayoutError), + #[error( + "The size of `{0}` on the gpu is not known at compile time. {1}'s {2} \ + requires that the size of {0} on the gpu is known at compile time." + )] + MustBeSized(TypeLayoutRecipe, Language, BufferAddressSpaceEnum), + #[error("Unknown layout error occured for {0} in {1}.")] + UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), +} + +#[derive(Debug, Clone)] +pub struct LayoutError { + recipe: TypeLayoutRecipe, + mismatch: LayoutMismatch, + + /// Used to adjust the error message + /// to fit `NotRepresentable` or `RequirementsNotSatisfied`. + kind: LayoutErrorKind, + language: Language, + address_space: BufferAddressSpaceEnum, + + colored: bool, +} + +#[derive(Debug, Clone, Copy)] +pub enum LayoutErrorKind { + NotRepresentable, + RequirementsNotSatisfied, +} + +impl LayoutError { + /// Returns the context the error occurred in. The "{language}" in case of a `NotRepresentable` error, + /// or the "{language}'s {address_space}" in case of a `RequirementsNotSatisfied` error. + fn context(&self) -> &'static str { + match self.kind { + LayoutErrorKind::NotRepresentable => match self.language { + Language::Wgsl => "wgsl", + }, + LayoutErrorKind::RequirementsNotSatisfied => match (self.language, self.address_space) { + (Language::Wgsl, BufferAddressSpaceEnum::Storage) => "wgsl's storage address space", + (Language::Wgsl, BufferAddressSpaceEnum::Uniform) => "wgsl's uniform address space", + }, + } + } +} + +impl std::error::Error for LayoutError {} +impl std::fmt::Display for LayoutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.kind { + LayoutErrorKind::NotRepresentable => { + writeln!(f, "`{}` is not representable in {}:", self.recipe, self.language)?; + } + LayoutErrorKind::RequirementsNotSatisfied => { + writeln!( + f, + "`{}` does not satisfy the layout requirements of {}:", + self.recipe, + self.context() + )?; + } + } + + match &self.mismatch { + LayoutMismatch::TopLevel { + layout_left, + layout_right, + mismatch, + } => write_top_level_mismatch(f, self, layout_left, layout_right, mismatch), + LayoutMismatch::Struct { + struct_left, + struct_right, + mismatch, + } => write_struct_mismatch(f, self, struct_left, struct_right, mismatch), + } + } +} + +fn write_top_level_mismatch( + f: &mut std::fmt::Formatter<'_>, + error: &LayoutError, + layout_left: &TypeLayout, + layout_right: &TypeLayout, + mismatch: &TopLevelMismatch, +) -> Result<(), std::fmt::Error> { + match mismatch { + TopLevelMismatch::Type => { + // unreachable: The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same + writeln!(f, "[internal error trying to find top-level type layout mismatch: the types seem identical, please report this error]"); + }, + TopLevelMismatch::ArrayStride { + array_left, + array_right, + } => { + let outer_most_array_has_mismatch = array_left.short_name() == layout_left.short_name(); + if outer_most_array_has_mismatch { + writeln!( + f, + "`{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + array_right.byte_stride, + error.context(), + array_left.byte_stride + )?; + } else { + writeln!( + f, + "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + layout_left.short_name(), + array_right.byte_stride, + error.context(), + array_left.byte_stride + )?; + } + } + TopLevelMismatch::ByteSize { left, right } => { + let outer_most_array_has_mismatch = left.short_name() == layout_left.short_name(); + if outer_most_array_has_mismatch { + writeln!( + f, + "`{}` has a byte size of {} in {}, but has a byte size of {}.", + layout_left.short_name(), + UnwrapOrStr(layout_left.byte_size(), ""), + error.context(), + UnwrapOrStr(layout_right.byte_size(), "") + )?; + } else { + writeln!( + f, + "`{}` in `{}` has a byte size of {} in {}, but has a byte size of {}.", + layout_left.short_name(), + layout_left.short_name(), + UnwrapOrStr(layout_left.byte_size(), ""), + error.context(), + UnwrapOrStr(layout_right.byte_size(), "") + )?; + } + } + } + Ok(()) +} + +fn write_struct_mismatch( + f: &mut std::fmt::Formatter<'_>, + error: &LayoutError, + struct_left: &StructLayout, + struct_right: &StructLayout, + mismatch: &StructMismatch, +) -> Result<(), std::fmt::Error> { + match mismatch { + StructMismatch::FieldLayout { + field_index, + field_left, + mismatch: + TopLevelMismatch::ArrayStride { + array_left, + array_right, + }, + .. + } => { + let outer_most_array_has_mismatch = field_left.ty.short_name() == array_left.short_name(); + let layout_info = if outer_most_array_has_mismatch { + LayoutInfoFlags::STRIDE + } else { + // if an inner array has the stride mismatch, showing the outer array's stride could be confusing + LayoutInfoFlags::NONE + }; + + writeln!( + f, + "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + array_left.short_name(), + struct_left.short_name(), + array_right.byte_stride, + error.context(), + array_left.byte_stride + )?; + writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; + write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; + } + StructMismatch::FieldLayout { + field_index, + field_left, + field_right, + mismatch: TopLevelMismatch::ByteSize { left, right }, + } => { + let outer_most_array_has_mismatch = field_left.ty.short_name() == left.short_name(); + let layout_info = if outer_most_array_has_mismatch { + LayoutInfoFlags::SIZE + } else { + // if an inner array has the byte size mismatch, showing the outer array's byte size could be confusing + LayoutInfoFlags::NONE + }; + + if !outer_most_array_has_mismatch { + write!(f, "`{}` in field", left.short_name())?; + } else { + write!(f, "Field")?; + } + writeln!( + f, + " `{}` of `{}` requires a byte size of {} in {}, but has a byte size of {}", + field_left.name, + struct_left.name, + UnwrapOrStr(field_right.ty.byte_size(), ""), + error.context(), + UnwrapOrStr(field_left.ty.byte_size(), "") + )?; + writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; + write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; + } + StructMismatch::FieldOffset { + field_index, + field_left, + field_right, + } => { + let field_name = &field_left.name; + let offset = field_left.rel_byte_offset; + let expected_align = field_right.ty.align().as_u64(); + let actual_align = max_u64_po2_dividing(field_left.rel_byte_offset); + + writeln!( + f, + "Field `{}` of `{}` needs to be {} byte aligned in {}, but has a byte-offset of {}, which is only {} byte aligned", + field_name, + struct_left.name, + expected_align, + error.context(), + offset, + actual_align + )?; + + writeln!(f, "The full layout of `{}` is:\n", struct_left.short_name())?; + write_struct( + f, + struct_left, + LayoutInfoFlags::OFFSET | LayoutInfoFlags::ALIGN | LayoutInfoFlags::SIZE, + Some(*field_index), + error.colored, + )?; + + writeln!(f, "\nPotential solutions include:")?; + writeln!( + f, + "- add an #[align({})] attribute to the definition of `{}`", + field_right.ty.align().as_u32(), + field_name + )?; + writeln!( + f, + "- increase the offset of `{field_name}` until it is divisible by {expected_align} by making previous fields larger or adding fields before it" + )?; + match (error.kind, error.language, error.address_space) { + (LayoutErrorKind::RequirementsNotSatisfied, Language::Wgsl, BufferAddressSpaceEnum::Uniform) => { + writeln!(f, "- use a storage binding instead of a uniform binding")?; + } + ( + LayoutErrorKind::NotRepresentable | LayoutErrorKind::RequirementsNotSatisfied, + Language::Wgsl, + BufferAddressSpaceEnum::Storage | BufferAddressSpaceEnum::Uniform, + ) => {} + } + writeln!(f)?; + + match error.language { + Language::Wgsl => { + match error.address_space { + BufferAddressSpaceEnum::Uniform => writeln!( + f, + "In {}, structs, arrays and array elements must be at least 16 byte aligned.", + error.context() + )?, + BufferAddressSpaceEnum::Storage => {} + } + + match (error.kind, error.address_space) { + ( + LayoutErrorKind::RequirementsNotSatisfied, + BufferAddressSpaceEnum::Uniform | BufferAddressSpaceEnum::Storage, + ) => writeln!( + f, + "More info about the wgsl's {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + error.address_space + )?, + (LayoutErrorKind::NotRepresentable, _) => writeln!( + f, + "More info about the wgsl's layout algorithm can be found at https://www.w3.org/TR/WGSL/#alignment-and-size" + )?, + } + } + } + } + StructMismatch::FieldCount | + StructMismatch::FieldName { .. } | + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::Type, + .. + } => { + // unreachable: The LayoutError is produced by comparing two semantically equivalent TypeLayouts, so all (nested) types are the same + writeln!(f, "[internal error trying to find struct layout mismatch: the structs seem identical, please report this error]"); + } + } + Ok(()) +} + +fn write_struct( + f: &mut W, + s: &StructLayout, + layout_info: LayoutInfoFlags, + highlight_field: Option, + colored: bool, +) -> std::fmt::Result +where + W: Write, +{ + let use_256_color_mode = false; + + let mut writer = s.writer(layout_info); + writer.writeln_header(f); + writer.writeln_struct_declaration(f); + for field_index in 0..s.fields.len() { + if Some(field_index) == highlight_field { + if colored { + set_color(f, Some("#508EE3"), use_256_color_mode)?; + } + writer.write_field(f, field_index)?; + writeln!(f, " <--")?; + if colored { + set_color(f, None, use_256_color_mode)?; + } + } else { + writer.writeln_field(f, field_index); + } + } + writer.writeln_struct_end(f)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pipeline_kind::Render; + use crate::{self as shame, EncodingGuard, ThreadIsAlreadyEncoding}; + use shame as sm; + use shame::{aliases::*, GpuLayout}; + + const PRINT: bool = false; + + macro_rules! is_struct_mismatch { + ($result:expr, $as_error:ident, $mismatch:pat) => {{ + if let Err(e) = &$result + && PRINT + { + println!("{e}"); + } + matches!( + $result, + Err(AddressSpaceError::$as_error($as_error::LayoutError(LayoutError { + mismatch: LayoutMismatch::Struct { + mismatch: $mismatch, + .. + }, + .. + }))) + ) + }}; + } + + fn enable_color() -> Option, ThreadIsAlreadyEncoding>> { + PRINT.then(|| sm::start_encoding(sm::Settings::default())) + } + + #[test] + fn field_offset_error_not_representable() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + #[gpu_repr(packed)] + struct A { + a: f32x1, + // has offset 4, but in wgsl's storage/uniform address space, it needs to be 16 byte aligned + b: f32x3, + } + + // The error variant is NotRepresentable, because there is no way to represent it in wgsl, + // because an offset of 4 is not possible for f32x3, because needs to be 16 byte aligned. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), + NotRepresentable, + StructMismatch::FieldOffset { .. } + )); + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), + NotRepresentable, + StructMismatch::FieldOffset { .. } + )); + } + + #[test] + fn wgsl_uniform_array_stride_requirements_not_satisfied() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + struct A { + a: f32x1, + // has stride 4, but wgsl's uniform address space requires a stride of 16. + // also, has wrong offset, because array align is multiple of 16 in wgsl's uniform address space. + // array stride error has higher priority than field offset error, + b: sm::Array>, + } + + // The error variant is RequirementsNotSatisfied, because the array has a stride of 4 in Repr::Packed, + // but wgsl's uniform address space requires a stride of 16. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::ArrayStride { .. }, + .. + } + )); + + // Testing that the error remains the same when nested in another struct + #[derive(sm::GpuLayout)] + struct B { + a: sm::Struct, + } + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, B::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::ArrayStride { .. }, + .. + } + )); + + // Testing that the error remains the same when nested in an array + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from( + Language::Wgsl, + , sm::Size<1>>>::layout_recipe() + ), + RequirementsNotSatisfied, + StructMismatch::FieldLayout { + mismatch: TopLevelMismatch::ArrayStride { .. }, + .. + } + )); + } + + #[test] + fn wgsl_uniform_field_offset_requirements_not_satisfied() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + struct A { + a: f32x1, + b: sm::Struct, + } + #[derive(sm::GpuLayout)] + struct B { + a: f32x1, + } + + // The error variant is RequirementsNotSatisfied, because the array has a stride of 4 in Repr::Packed, + // but wgsl's uniform address space requires a stride of 16. + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldOffset { .. } + )); + + // Testing that the error remains the same when nested in another struct + #[derive(sm::GpuLayout)] + struct C { + a: sm::Struct, + } + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from(Language::Wgsl, C::layout_recipe()), + RequirementsNotSatisfied, + StructMismatch::FieldOffset { .. } + )); + + // Testing that the error remains the same when nested in an array + assert!(is_struct_mismatch!( + TypeLayoutCompatibleWith::::try_from( + Language::Wgsl, + , sm::Size<1>>>::layout_recipe() + ), + RequirementsNotSatisfied, + StructMismatch::FieldOffset { .. } + )); + } + + #[test] + fn wgsl_uniform_must_be_sized() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + struct A { + a: sm::Array, + } + + let e = TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).unwrap_err(); + if PRINT { + println!("{e}"); + } + assert!(matches!( + e, + AddressSpaceError::RequirementsNotSatisfied(RequirementsNotSatisfied::MustBeSized( + _, + Language::Wgsl, + BufferAddressSpaceEnum::Uniform + )) + )); + + // Storage address space should allow unsized types + assert!(TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).is_ok()); + } + + #[test] + fn wgsl_storage_may_not_contain_packed_vec() { + let _guard = enable_color(); + + #[derive(sm::GpuLayout)] + #[gpu_repr(packed)] + struct A { + a: sm::packed::snorm16x2, + } + let e = TypeLayoutCompatibleWith::::try_from(Language::Wgsl, A::layout_recipe()).unwrap_err(); + if PRINT { + println!("{e}"); + } + assert!(matches!( + e, + AddressSpaceError::NotRepresentable(NotRepresentable::MayNotContain( + _, + Language::Wgsl, + BufferAddressSpaceEnum::Storage, + RecipeContains::PackedVector + )) + )); + } +} From d7a320c277f22aeeae6f868f9164f2c422889306 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 5 Jan 2026 18:06:36 +0100 Subject: [PATCH 163/182] rename `LayoutInfo` -> `LayoutInfoFlags` --- .../rust_types/type_layout/display.rs | 28 +++++++++---------- .../src/frontend/rust_types/type_layout/eq.rs | 18 ++++++------ shame/src/ir/ir_type/layout_constraints.rs | 8 +++--- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs index 890bd59..0763ddf 100644 --- a/shame/src/frontend/rust_types/type_layout/display.rs +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -14,7 +14,7 @@ use crate::{ }; impl Display for TypeLayout { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, LayoutInfo::ALL) } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.write(f, LayoutInfoFlags::ALL) } } impl TypeLayout { @@ -37,7 +37,7 @@ impl TypeLayout { } } - pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { + pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfoFlags) -> std::fmt::Result { use TypeLayout::*; match self { @@ -51,7 +51,7 @@ impl TypeLayout { let info_offset = plain.len() + 1; // Write header if some layout information is requested - if layout_info != LayoutInfo::NONE { + if layout_info != LayoutInfoFlags::NONE { writeln!(f, "{:info_offset$}{}", "", layout_info.header())?; } @@ -70,9 +70,9 @@ impl StructLayout { /// a short name for this `StructLayout`, useful for printing inline pub fn short_name(&self) -> String { self.name.to_string() } - pub(crate) fn writer(&self, layout_info: LayoutInfo) -> StructWriter<'_> { StructWriter::new(self, layout_info) } + pub(crate) fn writer(&self, layout_info: LayoutInfoFlags) -> StructWriter<'_> { StructWriter::new(self, layout_info) } - pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfo) -> std::fmt::Result { + pub(crate) fn write(&self, f: &mut W, layout_info: LayoutInfoFlags) -> std::fmt::Result { use TypeLayout::*; let mut writer = self.writer(layout_info); @@ -97,9 +97,9 @@ impl ArrayLayout { /// A bitmask that indicates which layout information should be displayed. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct LayoutInfo(u8); +pub struct LayoutInfoFlags(u8); #[rustfmt::skip] -impl LayoutInfo { +impl LayoutInfoFlags { pub const NONE: Self = Self(0); pub const OFFSET: Self = Self(1 << 0); pub const ALIGN: Self = Self(1 << 1); @@ -107,12 +107,12 @@ impl LayoutInfo { pub const STRIDE: Self = Self(1 << 3); pub const ALL: Self = Self(Self::OFFSET.0 | Self::ALIGN.0 | Self::SIZE.0 | Self::STRIDE.0); } -impl std::ops::BitOr for LayoutInfo { +impl std::ops::BitOr for LayoutInfoFlags { type Output = Self; - fn bitor(self, rhs: Self) -> Self::Output { LayoutInfo(self.0 | rhs.0) } + fn bitor(self, rhs: Self) -> Self::Output { LayoutInfoFlags(self.0 | rhs.0) } } -impl LayoutInfo { +impl LayoutInfoFlags { pub fn contains(&self, other: Self) -> bool { (self.0 & other.0) == other.0 } pub fn header(&self) -> String { @@ -150,12 +150,12 @@ impl LayoutInfo { pub struct StructWriter<'a> { s: &'a StructLayout, tab: &'static str, - layout_info: LayoutInfo, + layout_info: LayoutInfoFlags, layout_info_offset: usize, } impl<'a> StructWriter<'a> { - pub fn new(s: &'a StructLayout, layout_info: LayoutInfo) -> Self { + pub fn new(s: &'a StructLayout, layout_info: LayoutInfoFlags) -> Self { let mut this = Self { s, // Could make this configurable @@ -201,7 +201,7 @@ impl<'a> StructWriter<'a> { } pub(crate) fn write_header(&self, f: &mut W) -> std::fmt::Result { - if self.layout_info != LayoutInfo::NONE { + if self.layout_info != LayoutInfoFlags::NONE { let info_offset = self.layout_info_offset(); write!(f, "{:info_offset$}{}", "", self.layout_info.header())?; } @@ -240,7 +240,7 @@ impl<'a> StructWriter<'a> { pub(crate) fn write_struct_end(&self, f: &mut W) -> std::fmt::Result { write!(f, "}}") } pub(crate) fn writeln_header(&self, f: &mut W) -> std::fmt::Result { - if self.layout_info != LayoutInfo::NONE { + if self.layout_info != LayoutInfoFlags::NONE { self.write_header(f)?; writeln!(f)?; } diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index a97a74a..6dfdbf7 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,5 +1,5 @@ use crate::common::prettify::UnwrapOrStr; -use crate::frontend::rust_types::type_layout::display::LayoutInfo; +use crate::frontend::rust_types::type_layout::display::LayoutInfoFlags; use super::*; @@ -353,7 +353,7 @@ impl CheckEqLayoutMismatch { let (mismatch_field_index, layout_info) = match &mismatch { StructMismatch::FieldName { field_index, .. } => { writeln!(f, "names of field {field_index} are different.")?; - (Some(field_index), LayoutInfo::NONE) + (Some(field_index), LayoutInfoFlags::NONE) } StructMismatch::FieldLayout { field_index, @@ -362,7 +362,7 @@ impl CheckEqLayoutMismatch { .. } => { writeln!(f, "type of `{}` is different.", field_left.name)?; - (Some(field_index), LayoutInfo::NONE) + (Some(field_index), LayoutInfoFlags::NONE) } StructMismatch::FieldLayout { field_index, @@ -384,10 +384,10 @@ impl CheckEqLayoutMismatch { )?; // Not showing byte size info, because it can be misleading since // the inner type is the one that has mismatching byte size. - (Some(field_index), LayoutInfo::NONE) + (Some(field_index), LayoutInfoFlags::NONE) } else { writeln!(f, "byte size of `{}` is different.", field_left.name)?; - (Some(field_index), LayoutInfo::SIZE) + (Some(field_index), LayoutInfoFlags::SIZE) } } StructMismatch::FieldLayout { @@ -413,10 +413,10 @@ impl CheckEqLayoutMismatch { )?; // Not showing stride info, because it can be misleading since // the inner type is the one that has mismatching stride. - (Some(field_index), LayoutInfo::NONE) + (Some(field_index), LayoutInfoFlags::NONE) } else { writeln!(f, "array stride of {} is different.", field_left.name)?; - (Some(field_index), LayoutInfo::STRIDE) + (Some(field_index), LayoutInfoFlags::STRIDE) } } StructMismatch::FieldOffset { @@ -427,12 +427,12 @@ impl CheckEqLayoutMismatch { writeln!(f, "offset of {} is different.", field_left.name)?; ( Some(field_index), - LayoutInfo::OFFSET | LayoutInfo::ALIGN | LayoutInfo::SIZE, + LayoutInfoFlags::OFFSET | LayoutInfoFlags::ALIGN | LayoutInfoFlags::SIZE, ) } StructMismatch::FieldCount => { writeln!(f, "number of fields is different.")?; - (None, LayoutInfo::NONE) + (None, LayoutInfoFlags::NONE) } }; writeln!(f)?; diff --git a/shame/src/ir/ir_type/layout_constraints.rs b/shame/src/ir/ir_type/layout_constraints.rs index c28028b..7dcedcc 100644 --- a/shame/src/ir/ir_type/layout_constraints.rs +++ b/shame/src/ir/ir_type/layout_constraints.rs @@ -8,7 +8,7 @@ use thiserror::Error; use crate::{ common::proc_macro_utils::CpuLayoutImplMismatch, - frontend::rust_types::type_layout::{display::LayoutInfo, eq::CheckEqLayoutMismatch, TypeLayout}, + frontend::rust_types::type_layout::{display::LayoutInfoFlags, eq::CheckEqLayoutMismatch, TypeLayout}, }; use crate::{ backend::language::Language, @@ -512,7 +512,7 @@ impl Display for ArrayStrideAlignmentError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write(f, LayoutInfo::ALL)?; + layout.write(f, LayoutInfoFlags::ALL)?; writeln!(f); }; writeln!( @@ -547,7 +547,7 @@ impl Display for ArrayStrideError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write(f, LayoutInfo::ALL)?; + layout.write(f, LayoutInfoFlags::ALL)?; writeln!(f); }; writeln!( @@ -582,7 +582,7 @@ impl Display for ArrayAlignmentError { ); if let Ok(layout) = TypeLayout::from_store_ty(self.ctx.top_level_type.clone()) { writeln!(f, "The full layout of `{}` is:", self.ctx.top_level_type); - layout.write(f, LayoutInfo::ALL)?; + layout.write(f, LayoutInfoFlags::ALL)?; writeln!(f); }; writeln!( From 77bb06a02890327033fd4b8641e9e7cbf97e6c1a Mon Sep 17 00:00:00 2001 From: RayMarch Date: Mon, 5 Jan 2026 20:26:20 +0100 Subject: [PATCH 164/182] #[track_caller] for cpu_layout and gpu_layout --- shame/src/frontend/rust_types/layout_traits.rs | 2 ++ shame_derive/src/derive_layout.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 4558781..9cb7dec 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -185,12 +185,14 @@ pub trait GpuLayout { /// println!("OnGpu:\n{}\n", OnGpu::gpu_layout()); /// println!("OnCpu:\n{}\n", OnCpu::cpu_layout()); /// ``` +#[track_caller] pub fn gpu_layout() -> TypeLayout { T::layout_recipe().layout() } /// (no documentation yet) // `CpuLayout::cpu_layout` exists, but this function exists for consistency with // the `gpu_layout` function. `GpuLayout::gpu_layout` does not exist, so that implementors // of `GpuLayout` can't overwrite it. +#[track_caller] pub fn cpu_layout() -> TypeLayout { T::cpu_layout() } pub(crate) fn cpu_type_name_and_layout(ctx: &Context) -> Option<(Cow<'static, str>, TypeLayout)> { diff --git a/shame_derive/src/derive_layout.rs b/shame_derive/src/derive_layout.rs index 1fb8a35..4e37afd 100644 --- a/shame_derive/src/derive_layout.rs +++ b/shame_derive/src/derive_layout.rs @@ -585,6 +585,7 @@ pub fn impl_for_struct( #(#field_type: #re::CpuAligned,)* #where_clause_predicates { + #[track_caller] fn cpu_layout() -> #re::TypeLayout { //use #re::CpuLayout // using `use` instead of `as #re::CpuAligned` allows for duck-traits to circumvent the orphan rule use #re::CpuAligned; From 61aa3dc39b89aad474d080101ee16d7f2570adc3 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Tue, 6 Jan 2026 19:57:33 +0100 Subject: [PATCH 165/182] make `TypeLayout`'s `Debug` impl use `Display` formatting --- shame/src/frontend/rust_types/type_layout/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/mod.rs b/shame/src/frontend/rust_types/type_layout/mod.rs index b16bfc1..b67492f 100644 --- a/shame/src/frontend/rust_types/type_layout/mod.rs +++ b/shame/src/frontend/rust_types/type_layout/mod.rs @@ -35,7 +35,7 @@ pub(crate) mod recipe; /// use shame as sm; /// assert_eq!(sm::cpu_layout::(), sm::gpu_layout>()); /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum TypeLayout { /// `vec` Vector(VectorLayout), @@ -51,6 +51,14 @@ pub enum TypeLayout { Struct(Rc), } +impl Debug for TypeLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // debug assertions should display the proper presentation of diffs, + // so we us the Display trait here, too + write!(f, "{}", self) + } +} + #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VectorLayout { From de6fb93807573e20fbc7ea38e5fe3735e77443b9 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Tue, 6 Jan 2026 21:12:28 +0100 Subject: [PATCH 166/182] fixed CpuLayout mismatch error --- shame/src/common/proc_macro_utils.rs | 40 +++++---- shame/tests/test_layout.rs | 121 +++++++++++++++++++++++++-- 2 files changed, 135 insertions(+), 26 deletions(-) diff --git a/shame/src/common/proc_macro_utils.rs b/shame/src/common/proc_macro_utils.rs index 1799dcf..843759a 100644 --- a/shame/src/common/proc_macro_utils.rs +++ b/shame/src/common/proc_macro_utils.rs @@ -70,12 +70,13 @@ pub enum ReprCError { #[derive(Debug, thiserror::Error, Clone)] pub enum CpuLayoutImplMismatch { UnexpectedSize { - type_name: String, + field_index: usize, + field_type_name: String, struct_name: &'static str, /// size that was expected by `std::mem::size_of` / `CpuAligned::CPU_SIZE` - expected: Option, + expected_field_size: Option, /// size that was provided by the (maybe user defined) impl of `CpuLayout` - cpu_layout_provided: Option, + cpu_layout_provided_field_size: Option, }, } @@ -83,27 +84,31 @@ impl std::fmt::Display for CpuLayoutImplMismatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CpuLayoutImplMismatch::UnexpectedSize { - type_name: t, + field_index, + field_type_name: t, struct_name, - expected: std_mem_size, - cpu_layout_provided: cpu_layout_impl_size, + expected_field_size: std_mem_size, + cpu_layout_provided_field_size: cpu_layout_impl_size, } => { - write!(f, "The `CpuLayout` implementation of `{t}` claims that `{t}` ")?; + let field_index_count_from_1 = field_index + 1; + write!(f, "Field {field_index_count_from_1} of struct `{struct_name}` has a type with a `CpuLayout` implementation that \ + claims this field is a `{t}` "); + match cpu_layout_impl_size { - Some(s) => write!(f, "has a compile-time known size of {s},")?, - None => write!(f, "is unsized at compile-time,")?, + Some(s) => write!(f, "with a byte-size of {s},"), + None => write!(f, "with a size unknown at compile time,"), }; - write!(f, "\nbut a `{t}` within `{struct_name}` was just observed ")?; + match std_mem_size { - Some(s) => write!(f, "having a compile-time known size of {s}")?, - None => write!(f, "being unsized")?, + Some(s) => write!(f, "\nbut this field has an actual byte-size of {s}"), + None => write!(f, "\nbut the size of this field is actually unknown at compile-time (unsized)"), }; writeln!(f, ".")?; writeln!( f, - "This is most likely caused by a mistake in the `shame::CpuLayout` implementation of {t} \ + "This is most likely caused by a mistake in the `shame::CpuLayout` implementation of this field's type \ or in the implementation of one of the types it is composed of. \ - The size must be equal to what `std::mem::size_of` returns." + The size of the layout returned in the `CpuLayout` implementation must be equal to what `std::mem::size_of` returns." )?; } } @@ -185,10 +190,11 @@ pub fn repr_c_struct_layout( .chain(std::iter::once({ if last_field.layout.byte_size() != last_field_trait_size { try_report_cpu_layout_impl_mismatch(CpuLayoutImplMismatch::UnexpectedSize { + field_index: first_fields_with_offsets_and_sizes.len(), struct_name, - type_name: last_field.layout.short_name(), - expected: last_field_trait_size, - cpu_layout_provided: last_field.layout.byte_size(), + field_type_name: last_field.layout.short_name(), + expected_field_size: last_field_trait_size, + cpu_layout_provided_field_size: last_field.layout.byte_size(), }); } diff --git a/shame/tests/test_layout.rs b/shame/tests/test_layout.rs index d77952f..d0b6414 100644 --- a/shame/tests/test_layout.rs +++ b/shame/tests/test_layout.rs @@ -97,6 +97,87 @@ fn fixed_by_align_size_attribute() { assert_eq!(gpu_layout::(), cpu_layout::()); } + + { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + #[size(16)] + b: f32x3, + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: f32x3_size16, + c: i32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + } + + { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: i32x1, + #[size(16)] + c: f32x3, // TODO(release) this should work even without #[size(16)], no? + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: i32, + c: f32x3_size16, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + } + + { + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x4, + b: f32x3, // align 16 + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32x4_cpu, + b: f32x3_align4, // de-facto 16 aligned + c: i32, + } + + assert_eq!(gpu_layout::(), cpu_layout::()); + } + + { + // this is the case where rust's idea that `size` must be multiple of `align` + // clashes with wgsl's `vec3f` + + #[derive(sm::GpuLayout)] + struct OnGpu { + a: f32x1, + b: f32x3, + c: i32x1, + } + + #[derive(sm::CpuLayout)] + #[repr(C)] + struct OnCpu { + a: f32, + b: f32x3_cpu, + c: i32, + } + + assert_ne!(gpu_layout::(), cpu_layout::()); + } } #[test] @@ -143,16 +224,31 @@ fn unsized_struct_layout_eq() { #[repr(C, align(16))] struct f32x4_cpu(pub [f32; 4]); +impl CpuLayout for f32x4_cpu { + fn cpu_layout() -> shame::TypeLayout { + rust_layout_with_shame_semantics::() + } +} + #[derive(Clone, Copy)] #[repr(C, align(16))] struct f32x3_cpu(pub [f32; 3]); impl CpuLayout for f32x3_cpu { fn cpu_layout() -> shame::TypeLayout { - // TODO(release): replace this with `rust_layout_with_shame_semantics::()` - // and find a proper solution to the consequences. Its size is 16, and not 12. - let mut layout = gpu_layout::(); - *layout.align_mut() = Self::CPU_ALIGNMENT; + println!("this impl of `CpuLayout` is wrong, do not copy-paste it into your application"); + // This impl of `CpuLayout` is wrong. It claims that `Self` has size 12 + // and not size 16, as `std::mem::size_of::()` would return. + // It still represents a way a user might try to replicate wgsl's vec3f. + // At the time of writing it is undecided how we want to deal with this. + // The user can have an align4 and an align16 implementation of Vec3, similar + // to `glam`. The user could then choose depending on the desired packing. + // Atm this does not cause an actual memory bug, because actual offsets and + // sizes are used in cpu-layout checks. + + // TODO(release): decide on the above issue and, depending on decision, remove `f32x3_cpu` entirely + let mut layout = gpu_layout::(); // size 12 + *layout.align_mut() = Self::CPU_ALIGNMENT; // align 16 layout } } @@ -198,17 +294,23 @@ impl CpuLayout for f32x3_align4 { } } +#[derive(Clone, Copy)] +#[repr(C, align(16))] +struct f32x3_size16(pub [f32; 3], [u8; 4]); + +impl CpuLayout for f32x3_size16 { + fn cpu_layout() -> shame::TypeLayout { + rust_layout_with_shame_semantics::() + } +} + #[derive(Clone, Copy)] #[repr(C, align(16))] struct f32x3_size32(pub [f32; 3], [u8; 20]); impl CpuLayout for f32x3_size32 { fn cpu_layout() -> shame::TypeLayout { - // TODO(release): replace this with `rust_layout_with_shame_semantics::()` - // and find a proper solution to the consequences. Its size is 16, and not 12. - let mut layout = gpu_layout::(); - *layout.align_mut() = Self::CPU_ALIGNMENT; - layout + rust_layout_with_shame_semantics::() } } @@ -310,6 +412,7 @@ fn unsized_array_layout_eq() { assert_eq!(gpu_layout::>(), cpu_layout::<[f32]>()); assert_eq!(gpu_layout::>(), cpu_layout::<[f32x3_cpu]>()); assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_align4]>()); + assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size16]>()); assert_ne!(gpu_layout::>(), cpu_layout::<[f32x3_size32]>()); } From 5b46ef0965a0eb979dceaba3a9ae3915a334aad4 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Wed, 7 Jan 2026 13:47:29 +0100 Subject: [PATCH 167/182] changed stride docs --- shame/src/frontend/rust_types/type_layout/recipe/align_size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs b/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs index 10d84f9..1c66cef 100644 --- a/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs +++ b/shame/src/frontend/rust_types/type_layout/recipe/align_size.rs @@ -355,7 +355,7 @@ pub const fn array_align(element_align: U32PowerOf2, repr: Repr) -> U32PowerOf2 } } -/// Returns an array's size=>stride (the distance between consecutive elements) given the alignment and size of its elements. +/// Returns an array's stride (the distance between consecutive elements) given the alignment and size of its elements. pub const fn array_stride(element_align: U32PowerOf2, element_size: u64, repr: Repr) -> u64 { let element_align = match repr { Repr::Wgsl => element_align, From 2249b52ca0b8d5696f021dbee66f532d6ca06d09 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Wed, 7 Jan 2026 14:07:59 +0100 Subject: [PATCH 168/182] minor changes --- examples/api_showcase/src/main.rs | 2 +- shame/src/frontend/encoding/io_iter.rs | 6 +++--- shame/src/ir/ir_type/tensor.rs | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/api_showcase/src/main.rs b/examples/api_showcase/src/main.rs index fe48593..757b5f6 100644 --- a/examples/api_showcase/src/main.rs +++ b/examples/api_showcase/src/main.rs @@ -1,4 +1,4 @@ -#![allow(unused, clippy::no_effect)] +#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)] use shame as sm; use shame::prelude::*; use shame::aliases::*; diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index c53513a..b31c427 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -107,10 +107,10 @@ impl VertexBuffer<'_, T> { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); - let attribs_and_stride = Attrib::get_attribs_and_stride(&layout, &location_counter).ok_or_else(|| { - ctx.push_error(FrontendError::MalformedVertexBufferLayout(layout).into()); + let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { + ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); InvalidReason::ErrorThatWasPushed }); diff --git a/shame/src/ir/ir_type/tensor.rs b/shame/src/ir/ir_type/tensor.rs index f89c490..4b87fa6 100644 --- a/shame/src/ir/ir_type/tensor.rs +++ b/shame/src/ir/ir_type/tensor.rs @@ -532,16 +532,16 @@ impl PackedVector { pub fn align(&self, repr: Repr) -> U32PowerOf2 { match repr { - Repr::Packed => return PACKED_ALIGN, - Repr::Wgsl | Repr::WgslUniform => {} + Repr::Packed => PACKED_ALIGN, + Repr::Wgsl | Repr::WgslUniform => { + let align = match self.byte_size() { + PackedVectorByteSize::_2 => SizedType::Vector(Len::X1, ScalarType::F16).align(), + PackedVectorByteSize::_4 => SizedType::Vector(Len::X1, ScalarType::U32).align(), + PackedVectorByteSize::_8 => SizedType::Vector(Len::X2, ScalarType::U32).align(), + }; + U32PowerOf2::try_from(align as u32).expect("the above all have power of 2 align") + } } - - let align = match self.byte_size() { - PackedVectorByteSize::_2 => SizedType::Vector(Len::X1, ScalarType::F16).align(), - PackedVectorByteSize::_4 => SizedType::Vector(Len::X1, ScalarType::U32).align(), - PackedVectorByteSize::_8 => SizedType::Vector(Len::X2, ScalarType::U32).align(), - }; - U32PowerOf2::try_from(align as u32).expect("the above all have power of 2 align") } } From 49ab78fb2d9b99ad02aa3242a4aed8efb146d978 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Wed, 7 Jan 2026 15:46:28 +0100 Subject: [PATCH 169/182] change duplicate field name error display --- shame/src/ir/ir_type/struct_.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shame/src/ir/ir_type/struct_.rs b/shame/src/ir/ir_type/struct_.rs index 3f1a574..55141ed 100644 --- a/shame/src/ir/ir_type/struct_.rs +++ b/shame/src/ir/ir_type/struct_.rs @@ -41,7 +41,7 @@ pub enum StructureDefinitionError { "runtime sized arrays are only allowed as the last field of a buffer-block struct. They are not allowed in sized structs." )] RuntimeSizedArrayNotAllowedInSizedStruct, - #[error("field names must be unique within a structure definition")] + #[error(transparent)] FieldNamesMustBeUnique(StructureFieldNamesMustBeUnique), } @@ -425,7 +425,8 @@ impl TryFrom> for BufferBlock { /// an error created if a struct contains two or more fields of the same name #[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Error)] +#[error("struct fields {} and {} have the same name. Field names must be unique within a structure definition", self.first_occurence + 1, self.second_occurence + 1)] pub struct StructureFieldNamesMustBeUnique { pub first_occurence: usize, pub second_occurence: usize, From f020d73d13958c3354272ed10e80b732a915d2b8 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Wed, 7 Jan 2026 23:56:36 +0100 Subject: [PATCH 170/182] made layout::ScalarType part of the public interface --- examples/shame_wgpu/src/conversion.rs | 2 +- shame/src/lib.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/shame_wgpu/src/conversion.rs b/examples/shame_wgpu/src/conversion.rs index 3bb0ff7..8ff55e0 100644 --- a/examples/shame_wgpu/src/conversion.rs +++ b/examples/shame_wgpu/src/conversion.rs @@ -449,7 +449,7 @@ fn color_writes(write_mask: smr::ChannelWrites) -> wgpu::ColorWrites { #[rustfmt::skip] fn vertex_format(format: smr::VertexAttribFormat) -> Result { - use shame::any::layout::ScalarType as S; + use shame::layout::ScalarType as S; use smr::Len as L; use wgpu::VertexFormat as W; let unsupported = Err(ShameToWgpuError::UnsupportedVertexAttribFormat(format)); diff --git a/shame/src/lib.rs b/shame/src/lib.rs index 5d0d1b2..e6d9325 100644 --- a/shame/src/lib.rs +++ b/shame/src/lib.rs @@ -423,6 +423,11 @@ pub mod results { pub type Dict = std::collections::BTreeMap; } +/// everything related to type layouts +pub mod layout { + pub use crate::frontend::rust_types::type_layout::recipe::ScalarType; +} + // #[doc(hidden)] interface starts here // (not part of the public api) From a6287c7dfc8ba8189c4858adf05517f0d77822c8 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 15:25:46 +0100 Subject: [PATCH 171/182] adjust duplicate struct field name display --- shame/src/common/format.rs | 15 ++++++++++++++- shame/src/ir/ir_type/struct_.rs | 9 +++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/shame/src/common/format.rs b/shame/src/common/format.rs index 76606fe..47c558b 100644 --- a/shame/src/common/format.rs +++ b/shame/src/common/format.rs @@ -1,7 +1,8 @@ -use std::fmt::Write; +use std::fmt::{Display, Write}; use crate::ir::recording::CallInfo; +/// Ordinal formatting suffix for numbers. /// for "1st" "2nd" "3rd", and the likes. for `1` returns `"st"` pub fn numeral_suffix(i: usize) -> &'static str { match i { @@ -134,3 +135,15 @@ pub fn write_error_excerpt(f: &mut impl Write, call_info: CallInfo, use_colors: Ok(()) } + +/// Turn a closure into a struct implementing [`Display`]. Code borrowed from `Typst` +pub fn display std::fmt::Result>(f: F) -> impl Display { + struct Wrapper(F); + + impl std::fmt::Result> Display for Wrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0(f) + } + } + Wrapper(f) +} \ No newline at end of file diff --git a/shame/src/ir/ir_type/struct_.rs b/shame/src/ir/ir_type/struct_.rs index 55141ed..ea6a06e 100644 --- a/shame/src/ir/ir_type/struct_.rs +++ b/shame/src/ir/ir_type/struct_.rs @@ -10,7 +10,7 @@ use thiserror::Error; use super::{align_of_array, canon_name::CanonName, round_up, LayoutError, SizedType, StoreType, Type}; use crate::{ call_info, - common::{iterator_ext::IteratorExt, po2::U32PowerOf2, pool::Key}, + common::{format::numeral_suffix, iterator_ext::IteratorExt, po2::U32PowerOf2, pool::Key}, ir::recording::{Context, Ident}, }; use crate::{ @@ -423,10 +423,15 @@ impl TryFrom> for BufferBlock { } } + + /// an error created if a struct contains two or more fields of the same name #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Error)] -#[error("struct fields {} and {} have the same name. Field names must be unique within a structure definition", self.first_occurence + 1, self.second_occurence + 1)] +#[error("{} and {} struct field have the same name. Field names must be unique within a structure definition", + numeral_suffix(self.first_occurence + 1), + numeral_suffix(self.second_occurence + 1) +)] pub struct StructureFieldNamesMustBeUnique { pub first_occurence: usize, pub second_occurence: usize, From 14cf916b4e3c091f161dd6203c96fa465d891832 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 15:30:24 +0100 Subject: [PATCH 172/182] change `TopLevelMismatch` error messages --- shame/src/common/prettify.rs | 8 +-- .../rust_types/type_layout/compatible_with.rs | 60 ++++++++----------- .../rust_types/type_layout/display.rs | 8 +-- .../src/frontend/rust_types/type_layout/eq.rs | 19 ++++-- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/shame/src/common/prettify.rs b/shame/src/common/prettify.rs index c8eef5e..09b95c1 100644 --- a/shame/src/common/prettify.rs +++ b/shame/src/common/prettify.rs @@ -31,12 +31,12 @@ pub fn set_color(w: &mut W, hexcode: Option<&str>, use_256_c } /// Implements `Display` to print `Some(T)` as `T` and `None` as the provided &'static str. -pub(crate) struct UnwrapOrStr(pub Option, pub &'static str); -impl std::fmt::Display for UnwrapOrStr { +pub(crate) struct UnwrapDisplayOr(pub Option, pub &'static str); +impl std::fmt::Display for UnwrapDisplayOr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - UnwrapOrStr(Some(s), _) => s.fmt(f), - UnwrapOrStr(None, s) => s.fmt(f), + UnwrapDisplayOr(Some(s), _) => s.fmt(f), + UnwrapDisplayOr(None, s) => s.fmt(f), } } } diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 9653518..73b89dd 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -1,20 +1,12 @@ use std::fmt::Write; use crate::{ - any::layout::StructLayout, - call_info, - common::prettify::{set_color, UnwrapOrStr}, - frontend::{ + BufferAddressSpace, Language, TypeLayout, any::layout::StructLayout, call_info, common::{format::display, prettify::{UnwrapDisplayOr, set_color}}, frontend::{ encoding::buffer::BufferAddressSpaceEnum, rust_types::type_layout::{ - display::LayoutInfoFlags, - eq::{try_find_mismatch, LayoutMismatch, StructMismatch, TopLevelMismatch}, - recipe::to_layout::RecipeContains, - ArrayLayout, + ArrayLayout, display::{self, LayoutInfoFlags}, eq::{LayoutMismatch, StructMismatch, TopLevelMismatch, try_find_mismatch}, recipe::to_layout::RecipeContains }, - }, - ir::{ir_type::max_u64_po2_dividing, recording::Context}, - mem, BufferAddressSpace, Language, TypeLayout, + }, ir::{ir_type::max_u64_po2_dividing, recording::Context}, mem }; use super::{recipe::TypeLayoutRecipe, Repr}; @@ -280,7 +272,7 @@ fn write_top_level_mismatch( } else { writeln!( f, - "`{}` in `{}` requires a stride of {} in {}, but has a stride of {}.", + "`{}` within `{}` requires a stride of {} in {}, but has a stride of {}.", array_left.short_name(), layout_left.short_name(), array_right.byte_stride, @@ -291,26 +283,26 @@ fn write_top_level_mismatch( } TopLevelMismatch::ByteSize { left, right } => { let outer_most_array_has_mismatch = left.short_name() == layout_left.short_name(); - if outer_most_array_has_mismatch { - writeln!( - f, - "`{}` has a byte size of {} in {}, but has a byte size of {}.", - layout_left.short_name(), - UnwrapOrStr(layout_left.byte_size(), ""), - error.context(), - UnwrapOrStr(layout_right.byte_size(), "") - )?; - } else { - writeln!( - f, - "`{}` in `{}` has a byte size of {} in {}, but has a byte size of {}.", - layout_left.short_name(), - layout_left.short_name(), - UnwrapOrStr(layout_left.byte_size(), ""), - error.context(), - UnwrapOrStr(layout_right.byte_size(), "") - )?; - } + + let left_name = left.short_name(); + let within_layout_left = display(|f| match outer_most_array_has_mismatch { + true => Ok(()), // don't mention nesting + false => write!(f, " within `{}`", layout_left.short_name()) + }); + let requires_a_byte_size_of_left_size = display(|f| match left.byte_size() { + Some(size) => write!(f, "requires a byte size of {size}"), + None => write!(f, "must be runtime-sized"), + }); + let constraint = error.context(); + let has_a_byte_size_of_right_size = display(|f| match right.byte_size() { + Some(size) => write!(f, "has a byte size of {size}"), + None => write!(f, "is runtime-sized"), + }); + + writeln!( + f, + "`{left_name}`{within_layout_left} {requires_a_byte_size_of_left_size} in {constraint}, but {has_a_byte_size_of_right_size}.", + )?; } } Ok(()) @@ -378,9 +370,9 @@ fn write_struct_mismatch( " `{}` of `{}` requires a byte size of {} in {}, but has a byte size of {}", field_left.name, struct_left.name, - UnwrapOrStr(field_right.ty.byte_size(), ""), + UnwrapDisplayOr(field_right.ty.byte_size(), ""), error.context(), - UnwrapOrStr(field_left.ty.byte_size(), "") + UnwrapDisplayOr(field_left.ty.byte_size(), "") )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; diff --git a/shame/src/frontend/rust_types/type_layout/display.rs b/shame/src/frontend/rust_types/type_layout/display.rs index 0763ddf..eb64d08 100644 --- a/shame/src/frontend/rust_types/type_layout/display.rs +++ b/shame/src/frontend/rust_types/type_layout/display.rs @@ -9,7 +9,7 @@ use crate::{ layout::{ArrayLayout, StructLayout}, U32PowerOf2, }, - common::prettify::UnwrapOrStr, + common::prettify::UnwrapDisplayOr, TypeLayout, }; @@ -132,10 +132,10 @@ impl LayoutInfoFlags { pub fn format(&self, offset: Option, align: U32PowerOf2, size: Option, stride: Option) -> String { let infos: [(Self, &'static str, &dyn Display); 4] = [ - (Self::OFFSET, "offset", &UnwrapOrStr(offset, "")), + (Self::OFFSET, "offset", &UnwrapDisplayOr(offset, "")), (Self::ALIGN, "align", &align.as_u32()), - (Self::SIZE, "size", &UnwrapOrStr(size, "")), - (Self::STRIDE, "stride", &UnwrapOrStr(stride, "")), + (Self::SIZE, "size", &UnwrapDisplayOr(size, "")), + (Self::STRIDE, "stride", &UnwrapDisplayOr(stride, "")), ]; let mut parts = Vec::with_capacity(4); for (info, info_str, value) in infos { diff --git a/shame/src/frontend/rust_types/type_layout/eq.rs b/shame/src/frontend/rust_types/type_layout/eq.rs index 6dfdbf7..afea5a9 100644 --- a/shame/src/frontend/rust_types/type_layout/eq.rs +++ b/shame/src/frontend/rust_types/type_layout/eq.rs @@ -1,4 +1,5 @@ -use crate::common::prettify::UnwrapOrStr; +use crate::common::format::display; +use crate::common::prettify::UnwrapDisplayOr; use crate::frontend::rust_types::type_layout::display::LayoutInfoFlags; use super::*; @@ -320,11 +321,17 @@ impl CheckEqLayoutMismatch { )?; writeln!( f, - "`{}` ({a_name}) has a byte size of {}, while `{}` ({b_name}) has a byte size of {}.", + "`{}` ({a_name}) {}, while `{}` ({b_name}) {}.", left.short_name(), - UnwrapOrStr(left.byte_size(), "runtime-sized"), + display(|f| match left.byte_size() { + Some(size) => write!(f, "has a byte-size of {size}"), + None => write!(f, "is runtime-sized"), + }), right.short_name(), - UnwrapOrStr(right.byte_size(), "runtime-sized") + display(|f| match right.byte_size() { + Some(size) => write!(f, "has a byte-size of {size}"), + None => write!(f, "is runtime-sized"), + }), )?; } }, @@ -376,10 +383,10 @@ impl CheckEqLayoutMismatch { f, "byte size of `{}` is {} in `{}` and the byte size of `{}` is {} in `{}`.", left.short_name(), - UnwrapOrStr(left.byte_size(), "runtime-sized"), + UnwrapDisplayOr(left.byte_size(), "runtime-sized"), struct_left.name, right.short_name(), - UnwrapOrStr(right.byte_size(), "runtime-sized"), + UnwrapDisplayOr(right.byte_size(), "runtime-sized"), struct_right.name, )?; // Not showing byte size info, because it can be misleading since From c878874291b866f8e4fb03d2b4c88d9b5a3affd9 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 15:49:05 +0100 Subject: [PATCH 173/182] switching left/right in error message --- .../src/frontend/rust_types/type_layout/compatible_with.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 73b89dd..1c6cc31 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -289,19 +289,19 @@ fn write_top_level_mismatch( true => Ok(()), // don't mention nesting false => write!(f, " within `{}`", layout_left.short_name()) }); - let requires_a_byte_size_of_left_size = display(|f| match left.byte_size() { + let requires_a_byte_size_of_right_size = display(|f| match right.byte_size() { Some(size) => write!(f, "requires a byte size of {size}"), None => write!(f, "must be runtime-sized"), }); let constraint = error.context(); - let has_a_byte_size_of_right_size = display(|f| match right.byte_size() { + let has_a_byte_size_of_left_size = display(|f| match left.byte_size() { Some(size) => write!(f, "has a byte size of {size}"), None => write!(f, "is runtime-sized"), }); writeln!( f, - "`{left_name}`{within_layout_left} {requires_a_byte_size_of_left_size} in {constraint}, but {has_a_byte_size_of_right_size}.", + "`{left_name}`{within_layout_left} {requires_a_byte_size_of_right_size} in {constraint}, but {has_a_byte_size_of_left_size}.", )?; } } @@ -365,6 +365,7 @@ fn write_struct_mismatch( } else { write!(f, "Field")?; } + writeln!( f, " `{}` of `{}` requires a byte size of {} in {}, but has a byte size of {}", From 40f2cdd3dda13da0dd6e363944312d318348e7ec Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 16:00:01 +0100 Subject: [PATCH 174/182] rewrote `StructMismatch::FieldLayout` message --- .../rust_types/type_layout/compatible_with.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 1c6cc31..40b875c 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -366,14 +366,21 @@ fn write_struct_mismatch( write!(f, "Field")?; } + let field_left_name = &field_left.name; + let struct_left_name = &struct_left.name; + let requires_a_byte_size_of_right_size = display(|f| match field_right.ty.byte_size() { + Some(size) => write!(f, "requires a byte size of {size}"), + None => write!(f, "must be runtime-sized"), + }); + let constraint = error.context(); + let has_a_byte_size_of_left_size = display(|f| match field_left.ty.byte_size() { + Some(size) => write!(f, "has a byte size of {size}"), + None => write!(f, "is actually runtime-sized"), + }); + writeln!( f, - " `{}` of `{}` requires a byte size of {} in {}, but has a byte size of {}", - field_left.name, - struct_left.name, - UnwrapDisplayOr(field_right.ty.byte_size(), ""), - error.context(), - UnwrapDisplayOr(field_left.ty.byte_size(), "") + " `{field_left_name}` of `{struct_left_name}` {requires_a_byte_size_of_right_size} in {constraint}, but {has_a_byte_size_of_left_size}", )?; writeln!(f, "The full layout of `{}` is:", struct_left.short_name())?; write_struct(f, struct_left, layout_info, Some(*field_index), error.colored)?; From 7fac5459868375312b2c0b6849bb8583b06afaf4 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 16:07:26 +0100 Subject: [PATCH 175/182] change `BufferAddressSpaceEnum` `Display` impl --- shame/src/frontend/encoding/buffer.rs | 4 ++-- .../frontend/rust_types/type_layout/compatible_with.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index 418bbbc..b9b17b3 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -51,8 +51,8 @@ impl BufferAddressSpace for mem::Storage { impl std::fmt::Display for BufferAddressSpaceEnum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - BufferAddressSpaceEnum::Storage => write!(f, "storage address space"), - BufferAddressSpaceEnum::Uniform => write!(f, "uniform address space"), + BufferAddressSpaceEnum::Storage => write!(f, "storage"), + BufferAddressSpaceEnum::Uniform => write!(f, "uniform"), } } } diff --git a/shame/src/frontend/rust_types/type_layout/compatible_with.rs b/shame/src/frontend/rust_types/type_layout/compatible_with.rs index 40b875c..560dfa3 100644 --- a/shame/src/frontend/rust_types/type_layout/compatible_with.rs +++ b/shame/src/frontend/rust_types/type_layout/compatible_with.rs @@ -156,9 +156,9 @@ pub enum AddressSpaceError { pub enum NotRepresentable { #[error("{0}")] LayoutError(LayoutError), - #[error("{0} contains a {3}, which is not allowed in {1}'s {2}.")] + #[error("{0} contains a {3}, which is not allowed in {1}'s {2} address space.")] MayNotContain(TypeLayoutRecipe, Language, BufferAddressSpaceEnum, RecipeContains), - #[error("Unknown layout error occured for {0} in {1}.")] + #[error("Unknown layout error occured for {0} in {1} address space.")] UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), } @@ -167,11 +167,11 @@ pub enum RequirementsNotSatisfied { #[error("{0}")] LayoutError(LayoutError), #[error( - "The size of `{0}` on the gpu is not known at compile time. {1}'s {2} \ + "The size of `{0}` on the gpu is not known at compile time. {1}'s {2} address space \ requires that the size of {0} on the gpu is known at compile time." )] MustBeSized(TypeLayoutRecipe, Language, BufferAddressSpaceEnum), - #[error("Unknown layout error occured for {0} in {1}.")] + #[error("Unknown layout error occured for {0} in {1} address space.")] UnknownLayoutError(TypeLayoutRecipe, BufferAddressSpaceEnum), } @@ -455,7 +455,7 @@ fn write_struct_mismatch( BufferAddressSpaceEnum::Uniform | BufferAddressSpaceEnum::Storage, ) => writeln!( f, - "More info about the wgsl's {} can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", + "More info about the wgsl's {} address space can be found at https://www.w3.org/TR/WGSL/#address-space-layout-constraints", error.address_space )?, (LayoutErrorKind::NotRepresentable, _) => writeln!( From 523a64a7604b433f180310af1d3fa75c533e785c Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 16:09:31 +0100 Subject: [PATCH 176/182] adjust comment --- shame/src/frontend/rust_types/layout_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 9cb7dec..8aff51f 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -136,7 +136,7 @@ use std::rc::Rc; /// [`StorageTexture`]: crate::StorageTexture /// pub trait GpuLayout { - /// Returns a [`TypeLayoutRecipe`] that describes how the type is laid out in memory. + /// Returns a [`TypeLayoutRecipe`] that describes how a layout algorithm (repr) should layout this type in memory. fn layout_recipe() -> TypeLayoutRecipe; /// For `GpuSized` types, this returns the [`SizedType`] that describes the type's layout. From 69b429d7450c0a10bf52b0331a3f8cb6e99f5a52 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 16:16:28 +0100 Subject: [PATCH 177/182] added cpu stride from size identity function for clarity --- shame/src/frontend/rust_types/layout_traits.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 8aff51f..4735b35 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -225,6 +225,11 @@ pub(crate) fn get_layout_compare_with_cpu_push_error( gpu_layout } +fn repr_c_array_stride_from_array_element_size(element_size: u64) -> u64 { + // in repr(C) the stride is equal to the element size + element_size +} + pub(crate) fn check_layout_push_error( ctx: &Context, cpu_name: &str, @@ -245,9 +250,10 @@ pub(crate) fn check_layout_push_error( (Some(_), None) => Err(LayoutError::UnsizedStride { name: gpu_layout.short_name(), }), + (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = cpu_size; - // + let cpu_stride = repr_c_array_stride_from_array_element_size(cpu_size); + let gpu_stride = array_stride(gpu_layout.align(), gpu_size, Repr::default()); if cpu_stride != gpu_stride { From 2f95a6596aa9b8d5db7c651d502e88204475401c Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 16:52:26 +0100 Subject: [PATCH 178/182] change stride_check argument --- shame/src/frontend/encoding/buffer.rs | 6 +- shame/src/frontend/encoding/io_iter.rs | 52 +++++++---------- .../src/frontend/rust_types/layout_traits.rs | 57 ++++++++++--------- 3 files changed, 53 insertions(+), 62 deletions(-) diff --git a/shame/src/frontend/encoding/buffer.rs b/shame/src/frontend/encoding/buffer.rs index b9b17b3..c94b640 100644 --- a/shame/src/frontend/encoding/buffer.rs +++ b/shame/src/frontend/encoding/buffer.rs @@ -125,9 +125,8 @@ where { #[track_caller] fn new(args: Result) -> Self { - let skip_stride_check = true; // not a vertex buffer Context::try_with(call_info!(), |ctx| { - get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check) + get_layout_compare_with_cpu_push_error::(ctx, None) }); Self { inner: T::instantiate_buffer_inner(args, BufferInner::::binding_type(DYN_OFFSET)), @@ -143,9 +142,8 @@ where { #[track_caller] fn new(args: Result) -> Self { - let skip_stride_check = true; // not a vertex buffer Context::try_with(call_info!(), |ctx| { - get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check) + get_layout_compare_with_cpu_push_error::(ctx, None) }); Self { inner: T::instantiate_buffer_ref_inner(args, BufferRefInner::::binding_type(DYN_OFFSET)), diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index b31c427..4bdf298 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -2,42 +2,25 @@ use std::{cell::Cell, iter, marker::PhantomData, rc::Rc}; use crate::{ - call_info, - common::integer::post_inc_u32, - frontend::{ + any::layout::Repr, call_info, common::integer::post_inc_u32, frontend::{ any::{ - render_io::{Attrib, Location, VertexAttribFormat, VertexBufferLayout}, - shared_io::{BindPath, BindingType}, - Any, InvalidReason, + Any, InvalidReason, render_io::{Attrib, Location, VertexAttribFormat, VertexBufferLayout}, shared_io::{BindPath, BindingType} }, error::InternalError, rust_types::{ - error::FrontendError, - layout_traits::{ - cpu_type_name_and_layout, get_layout_compare_with_cpu_push_error, ArrayElementsUnsizedError, FromAnys, - GpuLayout, VertexLayout, - }, - reference::AccessMode, - struct_::SizedFields, - type_traits::{BindingArgs, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools}, - GpuType, + GpuType, error::FrontendError, layout_traits::{ + ArrayElementsUnsizedError, FromAnys, GpuLayout, VertexLayout, cpu_type_name_and_layout, get_layout_compare_with_cpu_push_error + }, reference::AccessMode, struct_::SizedFields, type_traits::{BindingArgs, GpuSized, GpuStore, GpuStoreImplCategory, NoAtomics, NoBools} }, texture::{ - texture_array::{StorageTextureArray, TextureArray}, - texture_traits::{ + Sampler, Texture, TextureKind, texture_array::{StorageTextureArray, TextureArray}, texture_traits::{ LayerCoords, SamplingFormat, SamplingMethod, Spp, StorageTextureCoords, StorageTextureFormat, SupportsCoords, SupportsSpp, TextureCoords, - }, - Sampler, Texture, TextureKind, + } }, - }, - ir::{ - self, - ir_type::{Field, LayoutError}, - pipeline::{PipelineError, StageMask}, - recording::Context, - TextureFormatWrapper, - }, + }, ir::{ + self, TextureFormatWrapper, ir_type::{Field, LayoutError}, pipeline::{PipelineError, StageMask}, recording::Context + } }; use super::{binding::Binding, rasterizer::VertexIndex}; @@ -106,8 +89,16 @@ impl VertexBuffer<'_, T> { fn new(slot: u32, location_counter: Rc) -> Self { let call_info = call_info!(); let attribs_and_stride = Context::try_with(call_info, |ctx| { - let skip_stride_check = false; // it is implied that T is in an array, the strides must match - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); + // it is implied that T is in an array, the strides must match + // + // the stride check repr only affects vertex buffers where `T = f32x3`. + // In those cases we assume a stride of 16 bytes, so that the stride of `T` is + // identical to what it would be in an `array`. If the `T` itself is a struct that + // uses #[gpu_repr(packed)], that makes `T`s alignment equal to 1 and therefore the + // chosen repr here doesn't matter. + let stride_check = Some(Repr::default()); + + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, stride_check); let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); @@ -563,9 +554,8 @@ impl PushConstants<'_> { let _caller_scope = Context::call_info_scope(); // the push constants structure as a whole doesn't need to have the same stride - let skip_stride_check = true; Context::try_with(call_info!(), |ctx| { - let _ = get_layout_compare_with_cpu_push_error::(ctx, skip_stride_check); + let _ = get_layout_compare_with_cpu_push_error::(ctx, None); match T::impl_category() { GpuStoreImplCategory::Fields(buffer_block) => match buffer_block.last_unsized_field() { diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 4735b35..5532254 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -214,13 +214,13 @@ pub(crate) fn cpu_type_name_and_layout(ctx: &Context) -> Option<(C /// returns the `TypeLayout` of `T` and pushes an error to the provided context if it is incompatible with its associated cpu layout pub(crate) fn get_layout_compare_with_cpu_push_error( ctx: &Context, - skip_stride_check: bool, + treat_as_array_element_with_stride_repr: Option, ) -> TypeLayout { const ERR_COMMENT: &str = "`GpuLayout` uses WGSL layout rules unless #[gpu_repr(packed)] is used.\nsee https://www.w3.org/TR/WGSL/#structure-member-layout\n`CpuLayout` uses #[repr(C)].\nsee https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.c.struct"; let gpu_layout = gpu_layout::(); if let Some((cpu_name, cpu_layout)) = cpu_type_name_and_layout::(ctx) { - check_layout_push_error(ctx, &cpu_name, &cpu_layout, &gpu_layout, skip_stride_check, ERR_COMMENT).ok(); + check_layout_push_error(ctx, &cpu_name, &cpu_layout, &gpu_layout, treat_as_array_element_with_stride_repr, ERR_COMMENT).ok(); } gpu_layout } @@ -235,36 +235,39 @@ pub(crate) fn check_layout_push_error( cpu_name: &str, cpu_layout: &TypeLayout, gpu_layout: &TypeLayout, - skip_stride_check: bool, + treat_as_array_element_with_stride_repr: Option, comment_on_mismatch_error: &str, ) -> Result<(), InvalidReason> { type_layout::eq::check_eq(("cpu", cpu_layout), ("gpu", gpu_layout)) .map_err(|e| LayoutError::LayoutMismatch(e, Some(comment_on_mismatch_error.to_string()))) .and_then(|_| { - if skip_stride_check { - Ok(()) - } else { - // the layout is an element in an array, so the strides need to match too - match (cpu_layout.byte_size(), gpu_layout.byte_size()) { - (None, None) | (None, Some(_)) => Err(LayoutError::UnsizedStride { name: cpu_name.into() }), - (Some(_), None) => Err(LayoutError::UnsizedStride { - name: gpu_layout.short_name(), - }), - - (Some(cpu_size), Some(gpu_size)) => { - let cpu_stride = repr_c_array_stride_from_array_element_size(cpu_size); - - let gpu_stride = array_stride(gpu_layout.align(), gpu_size, Repr::default()); - - if cpu_stride != gpu_stride { - Err(LayoutError::StrideMismatch { - cpu_name: cpu_name.into(), - cpu_stride, - gpu_name: gpu_layout.short_name(), - gpu_stride, - }) - } else { - Ok(()) + match treat_as_array_element_with_stride_repr { + None => { + Ok(()) + } + Some(stride_repr) => { + // the layout is an element in an array, so the strides need to match too + match (cpu_layout.byte_size(), gpu_layout.byte_size()) { + (None, None) | (None, Some(_)) => Err(LayoutError::UnsizedStride { name: cpu_name.into() }), + (Some(_), None) => Err(LayoutError::UnsizedStride { + name: gpu_layout.short_name(), + }), + + (Some(cpu_size), Some(gpu_size)) => { + let cpu_stride = repr_c_array_stride_from_array_element_size(cpu_size); + + let gpu_stride = array_stride(gpu_layout.align(), gpu_size, stride_repr); + + if cpu_stride != gpu_stride { + Err(LayoutError::StrideMismatch { + cpu_name: cpu_name.into(), + cpu_stride, + gpu_name: gpu_layout.short_name(), + gpu_stride, + }) + } else { + Ok(()) + } } } } From a82a5714bcc49fe3bc3406b85dc06735b4a34e2c Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 17:00:29 +0100 Subject: [PATCH 179/182] added `Repr` arg to `get_attribs_and_stride` --- shame/src/frontend/any/render_io.rs | 3 ++- shame/src/frontend/encoding/io_iter.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/any/render_io.rs b/shame/src/frontend/any/render_io.rs index 3e64fb0..014fb10 100644 --- a/shame/src/frontend/any/render_io.rs +++ b/shame/src/frontend/any/render_io.rs @@ -248,10 +248,11 @@ impl Attrib { pub(crate) fn get_attribs_and_stride( layout: &TypeLayout, mut location_counter: &LocationCounter, + stride_repr: Repr, ) -> Option<(Box<[Attrib]>, u64)> { let stride = { let size = layout.byte_size()?; - recipe::array_stride(layout.align(), size, Repr::Wgsl) + recipe::array_stride(layout.align(), size, stride_repr) }; use TypeLayout::*; diff --git a/shame/src/frontend/encoding/io_iter.rs b/shame/src/frontend/encoding/io_iter.rs index 4bdf298..7d541e9 100644 --- a/shame/src/frontend/encoding/io_iter.rs +++ b/shame/src/frontend/encoding/io_iter.rs @@ -96,11 +96,11 @@ impl VertexBuffer<'_, T> { // identical to what it would be in an `array`. If the `T` itself is a struct that // uses #[gpu_repr(packed)], that makes `T`s alignment equal to 1 and therefore the // chosen repr here doesn't matter. - let stride_check = Some(Repr::default()); + let stride_repr = Repr::default(); - let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, stride_check); + let gpu_layout = get_layout_compare_with_cpu_push_error::(ctx, Some(stride_repr)); - let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter).ok_or_else(|| { + let attribs_and_stride = Attrib::get_attribs_and_stride(&gpu_layout, &location_counter, stride_repr).ok_or_else(|| { ctx.push_error(FrontendError::MalformedVertexBufferLayout(gpu_layout).into()); InvalidReason::ErrorThatWasPushed }); From 830077aeba36926ff8fbb320c693bdd313e61f99 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 17:06:34 +0100 Subject: [PATCH 180/182] make `cpu_type_name_and_layout` for `PackedVec` be `None` --- shame/src/frontend/rust_types/layout_traits.rs | 6 ++++++ shame/src/frontend/rust_types/packed_vec.rs | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/shame/src/frontend/rust_types/layout_traits.rs b/shame/src/frontend/rust_types/layout_traits.rs index 5532254..ac18208 100644 --- a/shame/src/frontend/rust_types/layout_traits.rs +++ b/shame/src/frontend/rust_types/layout_traits.rs @@ -157,6 +157,12 @@ pub trait GpuLayout { /// /// If this association exists, this function returns the name and layout of /// that Cpu type, otherwise `None` is returned. + /// examples: + /// - vec: has no association like that + /// - PackedVec: has no association like that + /// - mat: has no association like that + /// - Array: has such an association if the inner type does + /// - Struct: has such an association if `T` does /// /// implementor note: if a nested type's `cpu_type_name_and_layout` returns `Some` /// this function _MUST NOT_ return `None`, as it would throw away assumptions diff --git a/shame/src/frontend/rust_types/packed_vec.rs b/shame/src/frontend/rust_types/packed_vec.rs index 6a358dc..6bd79c5 100644 --- a/shame/src/frontend/rust_types/packed_vec.rs +++ b/shame/src/frontend/rust_types/packed_vec.rs @@ -142,10 +142,7 @@ impl GpuLayout for PackedVec { } fn cpu_type_name_and_layout() -> Option, TypeLayout), ArrayElementsUnsizedError>> { - let sized_ty: recipe::SizedType = Self::layout_recipe_sized(); - let name = sized_ty.to_string().into(); - let layout = sized_ty.layout(Repr::default()); - Some(Ok((name, layout))) + None } } From e1bede7c549657ab9c6111ed21946dd1c3741d79 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 17:13:24 +0100 Subject: [PATCH 181/182] changed `NoXYZ` trait implementor note --- shame/src/frontend/rust_types/type_traits.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/shame/src/frontend/rust_types/type_traits.rs b/shame/src/frontend/rust_types/type_traits.rs index 7880ec3..927effc 100644 --- a/shame/src/frontend/rust_types/type_traits.rs +++ b/shame/src/frontend/rust_types/type_traits.rs @@ -183,8 +183,8 @@ pub trait GpuAligned { message = "`{Self}` may contain `bool`s, which have an unspecified memory footprint on the graphics device." )] // implementor note: -// NoXYZ traits must require GpuLayout or some other base trait, so that the -// error message isn't misleading for user provided types `T`. Those types will show +// NoXYZ traits should require some other base trait, so that the +// error message isn't misleading for user provided types `T`. Those types will then show // the base trait diagnostic, instead of "`T` contains `XYZ`" which it doesn't. /// types that don't contain booleans at any nesting level /// @@ -197,19 +197,20 @@ pub trait NoBools {} message = "`{Self}` may be or contain a `shame::Atomic` type. Atomics are usable via `shame::BufferRef<_, Storage, ReadWrite>` or via allocations in workgroup memory" )] // implementor note: -// NoXYZ traits must require GpuLayout or some other base trait, so that the -// error message isn't misleading for user provided types `T`. Those types will show +// NoXYZ traits should require some other base trait, so that the +// error message isn't misleading for user provided types `T`. Those types will then show // the base trait diagnostic, instead of "`T` contains `XYZ`" which it doesn't. /// types that don't contain atomics at any nesting level pub trait NoAtomics {} -// implementor note: -// NoXYZ traits must require GpuLayout or some other base trait, so that the -// error message isn't misleading for user provided types `T`. Those types will show -// the base trait diagnostic, instead of "`T` contains `XYZ`" which it doesn't. #[diagnostic::on_unimplemented( message = "`{Self}` may be or contain a handle type such as `Texture`, `Sampler`, `StorageTexture`." )] +// implementor note: +// NoXYZ traits should require some other base trait, so that the +// error message isn't misleading for user provided types `T`. Those types will then show +// the base trait diagnostic, instead of "`T` contains `XYZ`" which it doesn't. + /// Implemented by types that aren't/contain no textures, storage textures, their array variants or samplers pub trait NoHandles {} From 9e071c7ad735fa428812151db0cdf0a7220cf2a1 Mon Sep 17 00:00:00 2001 From: RayMarch Date: Thu, 8 Jan 2026 17:17:13 +0100 Subject: [PATCH 182/182] impl `From` --- shame/src/ir/ir_type/struct_.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shame/src/ir/ir_type/struct_.rs b/shame/src/ir/ir_type/struct_.rs index ea6a06e..25b0863 100644 --- a/shame/src/ir/ir_type/struct_.rs +++ b/shame/src/ir/ir_type/struct_.rs @@ -42,7 +42,7 @@ pub enum StructureDefinitionError { )] RuntimeSizedArrayNotAllowedInSizedStruct, #[error(transparent)] - FieldNamesMustBeUnique(StructureFieldNamesMustBeUnique), + FieldNamesMustBeUnique(#[from] StructureFieldNamesMustBeUnique), } pub trait Field { @@ -207,9 +207,7 @@ impl Struct { ctx.latest_user_caller(), ); }); - if let Err(e) = check_for_duplicate_field_names(&struct_.sized_fields, struct_.last_unsized.as_ref()) { - return Err(StructureDefinitionError::FieldNamesMustBeUnique(e)); - } + check_for_duplicate_field_names(&struct_.sized_fields, struct_.last_unsized.as_ref())?; Ok(struct_) }