From be22b034b8cf4e791660f423921361966669de25 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Sun, 7 Sep 2025 17:58:51 -0700 Subject: [PATCH] Support scalar pair ABI (T, U) pairs in entrypoints and regular functions are now supported. --- crates/rustc_codegen_spirv/src/abi.rs | 2 +- .../src/codegen_cx/entry.rs | 120 ++++++++++++------ .../scalar_pair/compute_value_pair_fail.rs | 12 ++ .../compute_value_pair_fail.stderr | 10 ++ .../ui/lang/abi/scalar_pair/f32_u32.rs | 12 ++ .../ui/lang/abi/scalar_pair/i32_i32.rs | 15 +++ .../ui/lang/abi/scalar_pair/mixed.rs | 14 ++ .../ui/lang/abi/scalar_pair/newtype.rs | 17 +++ .../ui/lang/abi/scalar_pair/newtype_fn.rs | 27 ++++ .../abi/scalar_pair/pair_input_fragment.rs | 12 ++ .../lang/abi/scalar_pair/pair_input_vertex.rs | 12 ++ .../ui/lang/abi/scalar_pair/tuple.rs | 12 ++ .../ui/lang/abi/scalar_pair/u32_u64.rs | 15 +++ .../ui/lang/abi/scalar_pair/u64_u32.rs | 16 +++ .../ui/lang/core/intrinsics/black_box.rs | 1 - .../ui/lang/core/intrinsics/black_box.stderr | 8 +- tests/difftests/tests/Cargo.lock | 17 +++ tests/difftests/tests/Cargo.toml | 2 + .../scalar_pair/scalar_pair-cpu/Cargo.toml | 10 ++ .../scalar_pair/scalar_pair-cpu/src/main.rs | 72 +++++++++++ .../scalar_pair/scalar_pair-gpu/Cargo.toml | 18 +++ .../scalar_pair/scalar_pair-gpu/src/lib.rs | 81 ++++++++++++ .../scalar_pair/scalar_pair-gpu/src/main.rs | 33 +++++ 23 files changed, 492 insertions(+), 46 deletions(-) create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.stderr create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/f32_u32.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/i32_i32.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/mixed.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/newtype.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/newtype_fn.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/pair_input_fragment.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/pair_input_vertex.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/tuple.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/u32_u64.rs create mode 100644 tests/compiletests/ui/lang/abi/scalar_pair/u64_u32.rs create mode 100644 tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/Cargo.toml create mode 100644 tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/src/main.rs create mode 100644 tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/Cargo.toml create mode 100644 tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/lib.rs create mode 100644 tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/main.rs diff --git a/crates/rustc_codegen_spirv/src/abi.rs b/crates/rustc_codegen_spirv/src/abi.rs index 3c0c025845..b023439354 100644 --- a/crates/rustc_codegen_spirv/src/abi.rs +++ b/crates/rustc_codegen_spirv/src/abi.rs @@ -655,7 +655,7 @@ pub fn scalar_pair_element_backend_type<'tcx>( ty: TyAndLayout<'tcx>, index: usize, ) -> Word { - let [a, b] = match ty.layout.backend_repr() { + let [a, b] = match ty.backend_repr { BackendRepr::ScalarPair(a, b) => [a, b], other => span_bug!( span, diff --git a/crates/rustc_codegen_spirv/src/codegen_cx/entry.rs b/crates/rustc_codegen_spirv/src/codegen_cx/entry.rs index 5de823d261..786cf50781 100644 --- a/crates/rustc_codegen_spirv/src/codegen_cx/entry.rs +++ b/crates/rustc_codegen_spirv/src/codegen_cx/entry.rs @@ -11,7 +11,10 @@ use rspirv::dr::Operand; use rspirv::spirv::{ Capability, Decoration, Dim, ExecutionModel, FunctionControl, StorageClass, Word, }; -use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, MiscCodegenMethods as _}; +use rustc_codegen_ssa::traits::{ + BaseTypeCodegenMethods, BuilderMethods, ConstCodegenMethods, LayoutTypeCodegenMethods, + MiscCodegenMethods as _, +}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::MultiSpan; use rustc_hir as hir; @@ -86,22 +89,7 @@ impl<'tcx> CodegenCx<'tcx> { }; for (arg_abi, hir_param) in fn_abi.args.iter().zip(hir_params) { match arg_abi.mode { - PassMode::Direct(_) => {} - PassMode::Pair(..) => { - // FIXME(eddyb) implement `ScalarPair` `Input`s, or change - // the `FnAbi` readjustment to only use `PassMode::Pair` for - // pointers to `!Sized` types, but not other `ScalarPair`s. - if !matches!(arg_abi.layout.ty.kind(), ty::Ref(..)) { - self.tcx.dcx().span_err( - hir_param.ty_span, - format!( - "entry point parameter type not yet supported \ - (`{}` has `ScalarPair` ABI but is not a `&T`)", - arg_abi.layout.ty - ), - ); - } - } + PassMode::Direct(_) | PassMode::Pair(..) => {} // FIXME(eddyb) support these (by just ignoring them) - if there // is any validation concern, it should be done on the types. PassMode::Ignore => self.tcx.dcx().span_fatal( @@ -442,6 +430,33 @@ impl<'tcx> CodegenCx<'tcx> { } = self.entry_param_deduce_from_rust_ref_or_value(entry_arg_abi.layout, hir_param, &attrs); let value_spirv_type = value_layout.spirv_type(hir_param.ty_span, self); + // In compute shaders, user-provided data must come from buffers or push + // constants, i.e. by-reference parameters. + if execution_model == ExecutionModel::GLCompute + && matches!(entry_arg_abi.mode, PassMode::Direct(_) | PassMode::Pair(..)) + && !matches!(entry_arg_abi.layout.ty.kind(), ty::Ref(..)) + && attrs.builtin.is_none() + { + let param_name = if let hir::PatKind::Binding(_, _, ident, _) = &hir_param.pat.kind { + ident.name.to_string() + } else { + "parameter".to_string() + }; + self.tcx + .dcx() + .struct_span_err( + hir_param.ty_span, + format!("compute entry parameter `{param_name}` must be by-reference",), + ) + .with_help(format!( + "consider changing the type to `&{}`", + entry_arg_abi.layout.ty + )) + .emit(); + // Keep this a hard error to stop compilation after emitting help. + self.tcx.dcx().abort_if_errors(); + } + let (var_id, spec_const_id) = match storage_class { // Pre-allocate the module-scoped `OpVariable` *Result* ID. Ok(_) => ( @@ -491,14 +506,6 @@ impl<'tcx> CodegenCx<'tcx> { vs layout:\n{value_layout:#?}", entry_arg_abi.layout.ty ); - if is_pair && !is_unsized { - // If PassMode is Pair, then we need to fill in the second part of the pair with a - // value. We currently only do that with unsized types, so if a type is a pair for some - // other reason (e.g. a tuple), we bail. - self.tcx - .dcx() - .span_fatal(hir_param.ty_span, "pair type not supported yet") - } // FIXME(eddyb) should this talk about "typed buffers" instead of "interface blocks"? // FIXME(eddyb) should we talk about "descriptor indexing" or // actually use more reasonable terms like "resource arrays"? @@ -621,8 +628,8 @@ impl<'tcx> CodegenCx<'tcx> { } } - let value_len = if is_pair { - // We've already emitted an error, fill in a placeholder value + let value_len = if is_pair && is_unsized { + // For wide references (e.g., slices), the second component is a length. Some(bx.undef(self.type_isize())) } else { None @@ -645,21 +652,54 @@ impl<'tcx> CodegenCx<'tcx> { _ => unreachable!(), } } else { - assert_matches!(entry_arg_abi.mode, PassMode::Direct(_)); - - let value = match storage_class { - Ok(_) => { + match entry_arg_abi.mode { + PassMode::Direct(_) => { + let value = match storage_class { + Ok(_) => { + assert_eq!(storage_class, Ok(StorageClass::Input)); + bx.load( + entry_arg_abi.layout.spirv_type(hir_param.ty_span, bx), + value_ptr.unwrap(), + entry_arg_abi.layout.align.abi, + ) + } + Err(SpecConstant { .. }) => { + spec_const_id.unwrap().with_type(value_spirv_type) + } + }; + call_args.push(value); + assert_eq!(value_len, None); + } + PassMode::Pair(..) => { + // Load both elements of the scalar pair from the input variable. assert_eq!(storage_class, Ok(StorageClass::Input)); - bx.load( - entry_arg_abi.layout.spirv_type(hir_param.ty_span, bx), - value_ptr.unwrap(), - entry_arg_abi.layout.align.abi, - ) + let layout = entry_arg_abi.layout; + let (a, b) = match layout.backend_repr { + rustc_abi::BackendRepr::ScalarPair(a, b) => (a, b), + other => span_bug!( + hir_param.ty_span, + "ScalarPair expected for entry param, found {other:?}" + ), + }; + let b_offset = a + .primitive() + .size(self) + .align_to(b.primitive().align(self).abi); + + let elem0_ty = self.scalar_pair_element_backend_type(layout, 0, false); + let elem1_ty = self.scalar_pair_element_backend_type(layout, 1, false); + + let base_ptr = value_ptr.unwrap(); + let ptr1 = bx.inbounds_ptradd(base_ptr, self.const_usize(b_offset.bytes())); + + let v0 = bx.load(elem0_ty, base_ptr, layout.align.abi); + let v1 = bx.load(elem1_ty, ptr1, layout.align.restrict_for_offset(b_offset)); + call_args.push(v0); + call_args.push(v1); + assert_eq!(value_len, None); } - Err(SpecConstant { .. }) => spec_const_id.unwrap().with_type(value_spirv_type), - }; - call_args.push(value); - assert_eq!(value_len, None); + _ => unreachable!(), + } } // FIXME(eddyb) check whether the storage class is compatible with the diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.rs b/tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.rs new file mode 100644 index 0000000000..7c6d7eba57 --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.rs @@ -0,0 +1,12 @@ +// compile-fail +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + w: (u32, u32), + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], +) { + out[0] = w.0 + w.1; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.stderr b/tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.stderr new file mode 100644 index 0000000000..a6b234b15e --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/compute_value_pair_fail.stderr @@ -0,0 +1,10 @@ +error: compute entry parameter `w` must be by-reference + --> $DIR/compute_value_pair_fail.rs:8:8 + | +8 | w: (u32, u32), + | ^^^^^^^^^^ + | + = help: consider changing the type to `&(u32, u32)` + +error: aborting due to 1 previous error + diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/f32_u32.rs b/tests/compiletests/ui/lang/abi/scalar_pair/f32_u32.rs new file mode 100644 index 0000000000..35c9285324 --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/f32_u32.rs @@ -0,0 +1,12 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &(f32, u32), +) { + out[0] = w.0.to_bits() ^ w.1; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/i32_i32.rs b/tests/compiletests/ui/lang/abi/scalar_pair/i32_i32.rs new file mode 100644 index 0000000000..f068febf71 --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/i32_i32.rs @@ -0,0 +1,15 @@ +// build-pass +// compile-flags: -C target-feature=+Int64 +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &(i32, i32), +) { + // Sum and reinterpret as u32 for output + let s = (w.0 as i64 + w.1 as i64) as i32; + out[0] = s as u32; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/mixed.rs b/tests/compiletests/ui/lang/abi/scalar_pair/mixed.rs new file mode 100644 index 0000000000..685cdb9ffc --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/mixed.rs @@ -0,0 +1,14 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &(u32, f32), +) { + let a = w.0; + let b_bits = w.1.to_bits(); + out[0] = a ^ b_bits; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/newtype.rs b/tests/compiletests/ui/lang/abi/scalar_pair/newtype.rs new file mode 100644 index 0000000000..4655d3acda --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/newtype.rs @@ -0,0 +1,17 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[repr(transparent)] +pub struct Wrap((u32, u32)); + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &Wrap, +) { + let a = (w.0).0; + let b = (w.0).1; + out[0] = a + b; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/newtype_fn.rs b/tests/compiletests/ui/lang/abi/scalar_pair/newtype_fn.rs new file mode 100644 index 0000000000..b5f5e63a0d --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/newtype_fn.rs @@ -0,0 +1,27 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[repr(transparent)] +pub struct Inner((u32, u32)); + +#[repr(transparent)] +pub struct Outer( + core::mem::ManuallyDrop, + core::marker::PhantomData<()>, +); + +#[inline(never)] +fn sum_outer(o: Outer) -> u32 { + // SAFETY: repr(transparent) guarantees same layout as `Inner`. + let i: Inner = unsafe { core::mem::ManuallyDrop::into_inner((o.0)) }; + (i.0).0 + (i.0).1 +} + +#[spirv(compute(threads(1)))] +pub fn main(#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32]) { + let i = Inner((5, 7)); + let o = Outer(core::mem::ManuallyDrop::new(i), core::marker::PhantomData); + out[0] = sum_outer(o); +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/pair_input_fragment.rs b/tests/compiletests/ui/lang/abi/scalar_pair/pair_input_fragment.rs new file mode 100644 index 0000000000..29867b7f2d --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/pair_input_fragment.rs @@ -0,0 +1,12 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[spirv(fragment)] +pub fn main( + #[spirv(flat)] pi: (u32, u32), + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] out: &mut [u32], +) { + out[0] = pi.0.wrapping_add(pi.1); +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/pair_input_vertex.rs b/tests/compiletests/ui/lang/abi/scalar_pair/pair_input_vertex.rs new file mode 100644 index 0000000000..daf3ecdb3e --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/pair_input_vertex.rs @@ -0,0 +1,12 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[spirv(vertex)] +pub fn main( + p: (u32, u32), + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] out: &mut [u32], +) { + out[0] = p.0.wrapping_add(p.1); +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/tuple.rs b/tests/compiletests/ui/lang/abi/scalar_pair/tuple.rs new file mode 100644 index 0000000000..d989480b07 --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/tuple.rs @@ -0,0 +1,12 @@ +// build-pass +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &(u32, u32), +) { + out[0] = w.0 + w.1; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/u32_u64.rs b/tests/compiletests/ui/lang/abi/scalar_pair/u32_u64.rs new file mode 100644 index 0000000000..f67ddc5bbf --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/u32_u64.rs @@ -0,0 +1,15 @@ +// build-pass +// compile-flags: -C target-feature=+Int64 +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &(u32, u64), +) { + let hi = (w.1 >> 32) as u32; + let lo = (w.1 & 0xFFFF_FFFF) as u32; + out[0] = w.0 ^ hi ^ lo; +} diff --git a/tests/compiletests/ui/lang/abi/scalar_pair/u64_u32.rs b/tests/compiletests/ui/lang/abi/scalar_pair/u64_u32.rs new file mode 100644 index 0000000000..69187d10bd --- /dev/null +++ b/tests/compiletests/ui/lang/abi/scalar_pair/u64_u32.rs @@ -0,0 +1,16 @@ +// build-pass +// compile-flags: -C target-feature=+Int64 +#![no_std] + +use spirv_std::spirv; + +#[spirv(compute(threads(1)))] +pub fn main( + #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32], + #[spirv(uniform, descriptor_set = 0, binding = 1)] w: &(u64, u32), +) { + // Fold 64-bit into 32-bit deterministically + let hi = (w.0 >> 32) as u32; + let lo = (w.0 & 0xFFFF_FFFF) as u32; + out[0] = hi ^ lo ^ w.1; +} diff --git a/tests/compiletests/ui/lang/core/intrinsics/black_box.rs b/tests/compiletests/ui/lang/core/intrinsics/black_box.rs index 4a6c85a76b..676f9177de 100644 --- a/tests/compiletests/ui/lang/core/intrinsics/black_box.rs +++ b/tests/compiletests/ui/lang/core/intrinsics/black_box.rs @@ -5,7 +5,6 @@ use core::hint::black_box; use spirv_std::spirv; -// Minimal kernel that writes the disassembly function result to a buffer #[spirv(compute(threads(1)))] pub fn main(#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] out: &mut [u32]) { let r = disassemble(); diff --git a/tests/compiletests/ui/lang/core/intrinsics/black_box.stderr b/tests/compiletests/ui/lang/core/intrinsics/black_box.stderr index 192a37e3a3..56700b0e32 100644 --- a/tests/compiletests/ui/lang/core/intrinsics/black_box.stderr +++ b/tests/compiletests/ui/lang/core/intrinsics/black_box.stderr @@ -2,15 +2,15 @@ warning: black_box intrinsic does not prevent optimization in Rust GPU %1 = OpFunction %2 DontInline %3 %4 = OpLabel -OpLine %5 32 17 +OpLine %5 31 17 %6 = OpIAdd %7 %8 %9 -OpLine %5 41 19 +OpLine %5 40 19 %10 = OpIAdd %7 %11 %12 -OpLine %5 47 8 +OpLine %5 46 8 %13 = OpBitcast %7 %14 OpLine %15 1092 17 %16 = OpBitcast %7 %17 -OpLine %5 46 4 +OpLine %5 45 4 %18 = OpCompositeConstruct %2 %13 %16 %19 %20 %21 %22 %6 %23 %10 %24 %24 %24 OpNoLine OpReturnValue %18 diff --git a/tests/difftests/tests/Cargo.lock b/tests/difftests/tests/Cargo.lock index 74d87cce66..4b925bda90 100644 --- a/tests/difftests/tests/Cargo.lock +++ b/tests/difftests/tests/Cargo.lock @@ -1332,6 +1332,23 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scalar_pair-cpu" +version = "0.0.0" +dependencies = [ + "bytemuck", + "difftest", +] + +[[package]] +name = "scalar_pair-gpu" +version = "0.0.0" +dependencies = [ + "bytemuck", + "difftest", + "spirv-std", +] + [[package]] name = "scopeguard" version = "1.2.0" diff --git a/tests/difftests/tests/Cargo.toml b/tests/difftests/tests/Cargo.toml index 7c74495325..a9c14ee67f 100644 --- a/tests/difftests/tests/Cargo.toml +++ b/tests/difftests/tests/Cargo.toml @@ -40,6 +40,8 @@ members = [ "lang/core/ops/vector_swizzle/vector_swizzle-wgsl", "lang/core/intrinsics/black_box_noop/with-black-box", "lang/core/intrinsics/black_box_noop/without-black-box", + "lang/abi/scalar_pair/scalar_pair-cpu", + "lang/abi/scalar_pair/scalar_pair-gpu", ] [workspace.package] diff --git a/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/Cargo.toml b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/Cargo.toml new file mode 100644 index 0000000000..d334ebc35c --- /dev/null +++ b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "scalar_pair-cpu" +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +difftest.workspace = true +bytemuck.workspace = true diff --git a/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/src/main.rs b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/src/main.rs new file mode 100644 index 0000000000..dcd1aeb12e --- /dev/null +++ b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-cpu/src/main.rs @@ -0,0 +1,72 @@ +use difftest::config::{Config, TestMetadata}; +use std::{fs, io::Write}; + +fn main() { + let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); + + let a: (u32, u32) = (10, 20); + let b: (u32, f32) = (7, 3.5); + let c: (f32, u32) = (2.25, 9); + let d: (u32, u32) = (0xDEADBEEF, 0x12345678); + let e: (i32, i32) = (-5, 12); + + let sum_a = a.0 + a.1; + let mix_b = b.0 ^ b.1.to_bits(); + let mix_c = c.1 ^ c.0.to_bits(); + let d_and = d.0 & d.1; + let d_or = d.0 | d.1; + let d_xor = d.0 ^ d.1; + let d_sum = d.0.wrapping_add(d.1); + let e_sum_u32 = e.0.wrapping_add(e.1) as u32; + + let w_sum = 3u32 + 4u32; + let reorder_sum = 1u32 + 2u32; + + let mut out = [0u32; 21]; + out[0] = a.0; + out[1] = a.1; + out[2] = sum_a; + out[3] = mix_b; + out[4] = mix_c; + out[5] = d_and; + out[6] = d_or; + out[7] = d_xor; + out[8] = d_sum; + out[9] = e_sum_u32; + out[10] = w_sum; + out[11] = reorder_sum; + + let p: (i32, u32) = (-123, 456); + let p_mix = (p.0 as u32).wrapping_add(p.1); + let q: (f32, f32) = (0.75, -1.5); + let q_mix = q.0.to_bits() ^ q.1.to_bits(); + let r_mix = 8u32 ^ 16u32 ^ 0.5f32.to_bits() ^ (-2.0f32).to_bits(); + out[12] = p_mix; + out[13] = q_mix; + out[14] = r_mix; + + let tt_a_sum = 1u32 + 2 + 3 + 4; + let tt_b_mix = 5u32 ^ 1.5f32.to_bits() ^ 2.25f32.to_bits() ^ 9u32; + out[15] = tt_a_sum; + out[16] = tt_b_mix; + + let call_sum = 11u32.wrapping_add(22u32); + let call_mix = 13u32 ^ (-0.75f32).to_bits(); + out[17] = call_sum; + out[18] = call_mix; + + // Push constants equivalent values (must match GPU push data) + let pc0: u32 = 100; + let pc1: u32 = 200; + let pc_sum = pc0.wrapping_add(pc1); + let pc_xor = pc0 ^ pc1; + out[19] = pc_sum; + out[20] = pc_xor; + + let mut file = fs::File::create(&config.output_path).expect("Failed to create output file"); + file.write_all(bytemuck::cast_slice(&out)) + .expect("Failed to write output"); + config + .write_metadata(&TestMetadata::u32()) + .expect("Failed to write metadata"); +} diff --git a/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/Cargo.toml b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/Cargo.toml new file mode 100644 index 0000000000..2b8f3539a6 --- /dev/null +++ b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "scalar_pair-gpu" +edition.workspace = true + +[lints] +workspace = true + +[lib] +crate-type = ["dylib"] + +# GPU deps +[dependencies] +spirv-std.workspace = true + +# CPU deps (for the test harness) +[target.'cfg(not(target_arch = "spirv"))'.dependencies] +difftest.workspace = true +bytemuck.workspace = true diff --git a/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/lib.rs b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/lib.rs new file mode 100644 index 0000000000..90bdb394ef --- /dev/null +++ b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/lib.rs @@ -0,0 +1,81 @@ +#![no_std] + +use spirv_std::spirv; + +#[inline(never)] +fn sum_pair_u32(p: (u32, u32)) -> u32 { + p.0.wrapping_add(p.1) +} + +#[inline(never)] +fn mix_pair_u32_f32(p: (u32, f32)) -> u32 { + p.0 ^ p.1.to_bits() +} + +#[spirv(compute(threads(1)))] +pub fn main_cs( + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] out: &mut [u32], + #[spirv(push_constant)] pc: &(u32, u32), +) { + let a: (u32, u32) = (10, 20); + let b: (u32, f32) = (7, 3.5); + let c: (f32, u32) = (2.25, 9); + let d: (u32, u32) = (0xDEADBEEF, 0x12345678); + let e: (i32, i32) = (-5, 12); + + let sum_a = a.0 + a.1; + let mix_b = b.0 ^ b.1.to_bits(); + let mix_c = c.1 ^ c.0.to_bits(); + let d_and = d.0 & d.1; + let d_or = d.0 | d.1; + let d_xor = d.0 ^ d.1; + let d_sum = d.0.wrapping_add(d.1); + let e_sum_u32 = e.0.wrapping_add(e.1) as u32; + + #[repr(transparent)] + struct Wrap((u32, u32)); + let w = Wrap((3, 4)); + let w_sum = (w.0).0 + (w.0).1; + + let reorder_sum = (1u32, 2u32).1 + (1u32, 2u32).0; + + out[0] = a.0; + out[1] = a.1; + out[2] = sum_a; + out[3] = mix_b; + out[4] = mix_c; + out[5] = d_and; + out[6] = d_or; + out[7] = d_xor; + out[8] = d_sum; + out[9] = e_sum_u32; + out[10] = w_sum; + out[11] = reorder_sum; + + let p: (i32, u32) = (-123, 456); + let p_mix = (p.0 as u32).wrapping_add(p.1); + let q: (f32, f32) = (0.75, -1.5); + let q_mix = q.0.to_bits() ^ q.1.to_bits(); + let r: ((u32, f32), (u32, f32)) = ((8, 0.5), (16, -2.0)); + let r_mix = (r.0).0 ^ (r.1).0 ^ (r.0).1.to_bits() ^ (r.1).1.to_bits(); + out[12] = p_mix; + out[13] = q_mix; + out[14] = r_mix; + + let tt_a: ((u32, u32), (u32, u32)) = ((1, 2), (3, 4)); + let tt_a_sum = tt_a.0.0 + tt_a.0.1 + tt_a.1.0 + tt_a.1.1; + let tt_b: ((u32, f32), (f32, u32)) = ((5, 1.5), (2.25, 9)); + let tt_b_mix = tt_b.0.0 ^ tt_b.0.1.to_bits() ^ tt_b.1.0.to_bits() ^ tt_b.1.1; + out[15] = tt_a_sum; + out[16] = tt_b_mix; + + let call_sum = sum_pair_u32((11, 22)); + let call_mix = mix_pair_u32_f32((13, -0.75)); + out[17] = call_sum; + out[18] = call_mix; + + let pc_sum = pc.0.wrapping_add(pc.1); + let pc_xor = pc.0 ^ pc.1; + out[19] = pc_sum; + out[20] = pc_xor; +} diff --git a/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/main.rs b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/main.rs new file mode 100644 index 0000000000..b931ffa32e --- /dev/null +++ b/tests/difftests/tests/lang/abi/scalar_pair/scalar_pair-gpu/src/main.rs @@ -0,0 +1,33 @@ +use difftest::config::{Config, TestMetadata}; +use difftest::scaffold::compute::{ + BufferConfig, BufferUsage, RustComputeShader, WgpuComputeTestPushConstants, +}; + +fn main() { + let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); + // One storage buffer (output), plus push constants data (pair of u32) + let sizes = [21 * 4u64]; + let push_data: Vec = { + let a: u32 = 100; + let b: u32 = 200; + let mut v = Vec::with_capacity(8); + v.extend_from_slice(&a.to_le_bytes()); + v.extend_from_slice(&b.to_le_bytes()); + v + }; + let test = WgpuComputeTestPushConstants::new( + RustComputeShader::default(), + [1, 1, 1], + vec![BufferConfig { + size: sizes[0], + usage: BufferUsage::Storage, + initial_data: None, + }], + 8, + push_data, + ); + test.run_test(&config).unwrap(); + config + .write_metadata(&TestMetadata::u32()) + .expect("Failed to write metadata"); +}