use crate::error_format::ErrorFormatScratch;
pub(crate) const U32_BYTES: usize = std::mem::size_of::<u32>();
pub(crate) const U32_BITS_PER_WORD: u32 = u32::BITS;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum DispatchDecodeDirection {
Input,
Output,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct DispatchU32Schema<'a> {
pub name: &'a str,
pub words: usize,
pub direction: DispatchDecodeDirection,
}
impl<'a> DispatchU32Schema<'a> {
#[inline]
#[must_use]
pub(crate) const fn input(name: &'a str, words: usize) -> Self {
Self {
name,
words,
direction: DispatchDecodeDirection::Input,
}
}
#[inline]
#[must_use]
pub(crate) const fn output(name: &'a str, words: usize) -> Self {
Self {
name,
words,
direction: DispatchDecodeDirection::Output,
}
}
#[inline]
#[must_use]
pub(crate) const fn scalar_output(name: &'a str) -> Self {
Self::output(name, 1)
}
#[inline]
pub(crate) fn required_bytes(self) -> Result<usize, String> {
byte_len_for_u32_words(self.words, self.name)
}
#[inline]
pub(crate) fn require_input_words(self, got: usize) -> Result<(), String> {
if got != self.words {
return Err(input_word_count_mismatch(self.name, got, self.words));
}
Ok(())
}
#[inline]
pub(crate) fn require_output_bytes(self, got: usize) -> Result<(), String> {
let required = self.required_bytes()?;
if got != required {
return Err(output_byte_len_mismatch(self.name, got, required));
}
Ok(())
}
#[inline]
pub(crate) fn require_scalar_output_bytes(self, got: usize) -> Result<(), String> {
if got != U32_BYTES {
return Err(scalar_byte_len_mismatch(self.name, got));
}
Ok(())
}
}
#[inline]
pub(crate) fn byte_len_for_u32_words(words: usize, caller: &str) -> Result<usize, String> {
words.checked_mul(U32_BYTES).ok_or_else(|| {
byte_len_overflow(caller, words)
})
}
#[cold]
fn input_word_count_mismatch(name: &str, got: usize, expected: usize) -> String {
format!(
"{name} input has {got} u32 words, expected exactly {expected}. Fix: pass the declared semantic input width; Weir GPU wrappers never silently pad or truncate inputs."
)
}
#[cold]
fn output_byte_len_mismatch(name: &str, got: usize, required: usize) -> String {
let mut scratch = ErrorFormatScratch::default();
crate::error_format::write_dispatch_byte_len_mismatch(&mut scratch, name, got, required)
}
#[cold]
fn scalar_byte_len_mismatch(name: &str, got: usize) -> String {
let mut scratch = ErrorFormatScratch::default();
crate::error_format::write_scalar_byte_len_mismatch(&mut scratch, name, got)
}
#[cold]
fn byte_len_overflow(caller: &str, words: usize) -> String {
format!(
"{caller} word count {words} overflows host byte indexing. Fix: split the Weir GPU dispatch buffer before packing or decoding."
)
}
#[cfg(test)]
mod tests {
use super::{byte_len_for_u32_words, DispatchU32Schema, U32_BITS_PER_WORD, U32_BYTES};
#[test]
fn u32_schema_reports_canonical_widths() {
assert_eq!(U32_BYTES, 4);
assert_eq!(U32_BITS_PER_WORD, 32);
assert_eq!(
DispatchU32Schema::output("schema-test", 3)
.required_bytes()
.expect("three u32 words should fit bytes"),
12
);
}
#[test]
fn u32_schema_rejects_malformed_input_word_count() {
let err = DispatchU32Schema::input("schema input", 2)
.require_input_words(3)
.expect_err("wrong input word count must fail closed");
assert!(
err.contains("schema input input has 3 u32 words")
&& err.contains("expected exactly 2")
&& err.contains("never silently pad or truncate inputs"),
"unexpected diagnostic: {err}"
);
}
#[test]
fn u32_schema_rejects_malformed_output_byte_count() {
let err = DispatchU32Schema::output("schema output", 2)
.require_output_bytes(7)
.expect_err("wrong output byte count must fail closed");
assert!(
err.contains("schema output output has malformed byte length")
&& err.contains("got 7 bytes")
&& err.contains("expected exactly 8")
&& err.contains("no trailing bytes"),
"unexpected diagnostic: {err}"
);
}
#[test]
fn u32_schema_rejects_malformed_scalar_byte_count() {
let err = DispatchU32Schema::scalar_output("schema scalar")
.require_scalar_output_bytes(8)
.expect_err("wrong scalar byte count must fail closed");
assert!(
err.contains("schema scalar output has malformed byte length")
&& err.contains("expected exactly 4")
&& err.contains("scalar u32 output size"),
"unexpected diagnostic: {err}"
);
}
#[test]
fn u32_schema_checked_byte_length_overflow_is_actionable() {
let err = byte_len_for_u32_words(usize::MAX, "schema overflow")
.expect_err("overflowing byte length must fail closed");
assert!(
err.contains("schema overflow word count")
&& err.contains("overflows host byte indexing")
&& err.contains("split the Weir GPU dispatch buffer"),
"unexpected diagnostic: {err}"
);
}
}