weir/dispatch_decode/
schema.rs1use crate::error_format::ErrorFormatScratch;
8
9pub(crate) const U32_BYTES: usize = std::mem::size_of::<u32>();
10pub(crate) const U32_BITS_PER_WORD: u32 = u32::BITS;
11
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
13pub(crate) enum DispatchDecodeDirection {
14 Input,
15 Output,
16}
17
18#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19pub(crate) struct DispatchU32Schema<'a> {
20 pub name: &'a str,
21 pub words: usize,
22 pub direction: DispatchDecodeDirection,
23}
24
25impl<'a> DispatchU32Schema<'a> {
26 #[inline]
27 #[must_use]
28 pub(crate) const fn input(name: &'a str, words: usize) -> Self {
29 Self {
30 name,
31 words,
32 direction: DispatchDecodeDirection::Input,
33 }
34 }
35
36 #[inline]
37 #[must_use]
38 pub(crate) const fn output(name: &'a str, words: usize) -> Self {
39 Self {
40 name,
41 words,
42 direction: DispatchDecodeDirection::Output,
43 }
44 }
45
46 #[inline]
47 #[must_use]
48 pub(crate) const fn scalar_output(name: &'a str) -> Self {
49 Self::output(name, 1)
50 }
51
52 #[inline]
53 pub(crate) fn required_bytes(self) -> Result<usize, String> {
54 byte_len_for_u32_words(self.words, self.name)
55 }
56
57 #[inline]
58 pub(crate) fn require_input_words(self, got: usize) -> Result<(), String> {
59 if got != self.words {
60 return Err(input_word_count_mismatch(self.name, got, self.words));
61 }
62 Ok(())
63 }
64
65 #[inline]
66 pub(crate) fn require_output_bytes(self, got: usize) -> Result<(), String> {
67 let required = self.required_bytes()?;
68 if got != required {
69 return Err(output_byte_len_mismatch(self.name, got, required));
70 }
71 Ok(())
72 }
73
74 #[inline]
75 pub(crate) fn require_scalar_output_bytes(self, got: usize) -> Result<(), String> {
76 if got != U32_BYTES {
77 return Err(scalar_byte_len_mismatch(self.name, got));
78 }
79 Ok(())
80 }
81}
82
83#[inline]
84pub(crate) fn byte_len_for_u32_words(words: usize, caller: &str) -> Result<usize, String> {
85 words.checked_mul(U32_BYTES).ok_or_else(|| {
86 byte_len_overflow(caller, words)
88 })
89}
90
91#[cold]
92fn input_word_count_mismatch(name: &str, got: usize, expected: usize) -> String {
93 format!(
94 "{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."
95 )
96}
97
98#[cold]
99fn output_byte_len_mismatch(name: &str, got: usize, required: usize) -> String {
100 let mut scratch = ErrorFormatScratch::default();
101 crate::error_format::write_dispatch_byte_len_mismatch(&mut scratch, name, got, required)
102}
103
104#[cold]
105fn scalar_byte_len_mismatch(name: &str, got: usize) -> String {
106 let mut scratch = ErrorFormatScratch::default();
107 crate::error_format::write_scalar_byte_len_mismatch(&mut scratch, name, got)
108}
109
110#[cold]
111fn byte_len_overflow(caller: &str, words: usize) -> String {
112 format!(
113 "{caller} word count {words} overflows host byte indexing. Fix: split the Weir GPU dispatch buffer before packing or decoding."
114 )
115}
116
117#[cfg(test)]
118mod tests {
119 use super::{byte_len_for_u32_words, DispatchU32Schema, U32_BITS_PER_WORD, U32_BYTES};
120
121 #[test]
122 fn u32_schema_reports_canonical_widths() {
123 assert_eq!(U32_BYTES, 4);
124 assert_eq!(U32_BITS_PER_WORD, 32);
125 assert_eq!(
126 DispatchU32Schema::output("schema-test", 3)
127 .required_bytes()
128 .expect("three u32 words should fit bytes"),
129 12
130 );
131 }
132
133 #[test]
134 fn u32_schema_rejects_malformed_input_word_count() {
135 let err = DispatchU32Schema::input("schema input", 2)
136 .require_input_words(3)
137 .expect_err("wrong input word count must fail closed");
138 assert!(
139 err.contains("schema input input has 3 u32 words")
140 && err.contains("expected exactly 2")
141 && err.contains("never silently pad or truncate inputs"),
142 "unexpected diagnostic: {err}"
143 );
144 }
145
146 #[test]
147 fn u32_schema_rejects_malformed_output_byte_count() {
148 let err = DispatchU32Schema::output("schema output", 2)
149 .require_output_bytes(7)
150 .expect_err("wrong output byte count must fail closed");
151 assert!(
152 err.contains("schema output output has malformed byte length")
153 && err.contains("got 7 bytes")
154 && err.contains("expected exactly 8")
155 && err.contains("no trailing bytes"),
156 "unexpected diagnostic: {err}"
157 );
158 }
159
160 #[test]
161 fn u32_schema_rejects_malformed_scalar_byte_count() {
162 let err = DispatchU32Schema::scalar_output("schema scalar")
163 .require_scalar_output_bytes(8)
164 .expect_err("wrong scalar byte count must fail closed");
165 assert!(
166 err.contains("schema scalar output has malformed byte length")
167 && err.contains("expected exactly 4")
168 && err.contains("scalar u32 output size"),
169 "unexpected diagnostic: {err}"
170 );
171 }
172
173 #[test]
174 fn u32_schema_checked_byte_length_overflow_is_actionable() {
175 let err = byte_len_for_u32_words(usize::MAX, "schema overflow")
176 .expect_err("overflowing byte length must fail closed");
177 assert!(
178 err.contains("schema overflow word count")
179 && err.contains("overflows host byte indexing")
180 && err.contains("split the Weir GPU dispatch buffer"),
181 "unexpected diagnostic: {err}"
182 );
183 }
184}