virtio_accel_xdna/
artifact.rs1use virtio_accel_core::{ArtifactFormat, BackendError};
28
29pub const XDNA_PRECOMPILED_FORMAT: ArtifactFormat = match ArtifactFormat::new(0x5844_4e50) {
31 Some(format) => format,
32 None => unreachable!(),
33};
34
35const MAGIC: [u8; 4] = *b"XDNP";
36const VERSION: u32 = 2;
37const HEADER_LEN: usize = 4 + 4 + 4 + 4 + 4 + 8 + 8;
38
39#[derive(Clone, Debug)]
41pub struct PrecompiledArtifact<'a> {
42 pub xclbin: &'a [u8],
43 pub insts: &'a [u8],
44 pub entry: &'a str,
45 pub inputs: usize,
46 pub outputs: usize,
47 pub slot_bytes: Vec<u64>,
49}
50
51impl<'a> PrecompiledArtifact<'a> {
52 pub fn parse(bytes: &'a [u8]) -> Result<Self, BackendError> {
55 if bytes.len() < HEADER_LEN || bytes[0..4] != MAGIC {
56 return Err(BackendError::InvalidArgument);
57 }
58 let u32_at = |offset: usize| {
59 u32::from_le_bytes(bytes[offset..offset + 4].try_into().expect("4 bytes"))
60 };
61 let u64_at = |offset: usize| {
62 u64::from_le_bytes(bytes[offset..offset + 8].try_into().expect("8 bytes"))
63 };
64 if u32_at(4) != VERSION {
65 return Err(BackendError::Incompatible);
66 }
67 let inputs = u32_at(8) as usize;
68 let outputs = u32_at(12) as usize;
69 let entry_len = u32_at(16) as usize;
70 let xclbin_len = usize::try_from(u64_at(20)).map_err(|_| BackendError::InvalidArgument)?;
71 let insts_len = usize::try_from(u64_at(28)).map_err(|_| BackendError::InvalidArgument)?;
72
73 if inputs == 0 || outputs == 0 {
76 return Err(BackendError::InvalidArgument);
77 }
78 let total = match inputs.checked_add(outputs) {
79 Some(total) if total <= 256 => total,
80 _ => return Err(BackendError::InvalidArgument),
81 };
82 if insts_len % 4 != 0 || insts_len == 0 || xclbin_len == 0 {
84 return Err(BackendError::InvalidArgument);
85 }
86 let sizes_end = HEADER_LEN
87 .checked_add(total * 8)
88 .ok_or(BackendError::InvalidArgument)?;
89 let entry_end = sizes_end
90 .checked_add(entry_len)
91 .ok_or(BackendError::InvalidArgument)?;
92 let xclbin_end = entry_end
93 .checked_add(xclbin_len)
94 .ok_or(BackendError::InvalidArgument)?;
95 let insts_end = xclbin_end
96 .checked_add(insts_len)
97 .ok_or(BackendError::InvalidArgument)?;
98 if insts_end != bytes.len() {
99 return Err(BackendError::InvalidArgument);
100 }
101 let mut slot_bytes = Vec::with_capacity(total);
102 for index in 0..total {
103 let size = u64_at(HEADER_LEN + index * 8);
104 if size == 0 {
106 return Err(BackendError::InvalidArgument);
107 }
108 slot_bytes.push(size);
109 }
110 let entry = core::str::from_utf8(&bytes[sizes_end..entry_end])
111 .map_err(|_| BackendError::InvalidArgument)?;
112 if entry.is_empty() || entry.contains('\0') {
113 return Err(BackendError::InvalidArgument);
114 }
115 Ok(Self {
116 xclbin: &bytes[entry_end..xclbin_end],
117 insts: &bytes[xclbin_end..insts_end],
118 entry,
119 inputs,
120 outputs,
121 slot_bytes,
122 })
123 }
124}
125
126pub fn encode(
130 entry: &str,
131 input_bytes: &[u64],
132 output_bytes: &[u64],
133 xclbin: &[u8],
134 insts: &[u8],
135) -> Vec<u8> {
136 let total = input_bytes.len() + output_bytes.len();
137 let mut out =
138 Vec::with_capacity(HEADER_LEN + total * 8 + entry.len() + xclbin.len() + insts.len());
139 out.extend_from_slice(&MAGIC);
140 out.extend_from_slice(&VERSION.to_le_bytes());
141 out.extend_from_slice(&(input_bytes.len() as u32).to_le_bytes());
142 out.extend_from_slice(&(output_bytes.len() as u32).to_le_bytes());
143 out.extend_from_slice(&(entry.len() as u32).to_le_bytes());
144 out.extend_from_slice(&(xclbin.len() as u64).to_le_bytes());
145 out.extend_from_slice(&(insts.len() as u64).to_le_bytes());
146 for size in input_bytes.iter().chain(output_bytes) {
147 out.extend_from_slice(&size.to_le_bytes());
148 }
149 out.extend_from_slice(entry.as_bytes());
150 out.extend_from_slice(xclbin);
151 out.extend_from_slice(insts);
152 out
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn round_trips_through_encode_and_parse() {
161 let bytes = encode("MLIR_AIE", &[128, 64], &[256], &[0xAB; 40], &[0x11; 8]);
162 let parsed = PrecompiledArtifact::parse(&bytes).expect("valid container");
163 assert_eq!(parsed.entry, "MLIR_AIE");
164 assert_eq!((parsed.inputs, parsed.outputs), (2, 1));
165 assert_eq!(parsed.slot_bytes, [128, 64, 256]);
166 assert_eq!(parsed.xclbin, &[0xAB; 40]);
167 assert_eq!(parsed.insts, &[0x11; 8]);
168 }
169
170 #[test]
171 fn rejects_bad_magic_version_and_alignment() {
172 assert!(matches!(
173 PrecompiledArtifact::parse(b"nope"),
174 Err(BackendError::InvalidArgument)
175 ));
176 let mut bytes = encode("MLIR_AIE", &[4], &[4], &[0u8; 4], &[0u8; 4]);
177 bytes[4] = 1; assert!(matches!(
179 PrecompiledArtifact::parse(&bytes),
180 Err(BackendError::Incompatible)
181 ));
182 let bad = encode("MLIR_AIE", &[4], &[4], &[0u8; 4], &[0u8; 6]);
184 assert!(matches!(
185 PrecompiledArtifact::parse(&bad),
186 Err(BackendError::InvalidArgument)
187 ));
188 }
189
190 #[test]
191 fn rejects_missing_outputs_and_zero_sized_slots() {
192 let no_outputs = encode("MLIR_AIE", &[4], &[], &[0u8; 4], &[0u8; 4]);
194 assert!(matches!(
195 PrecompiledArtifact::parse(&no_outputs),
196 Err(BackendError::InvalidArgument)
197 ));
198 let no_inputs = encode("MLIR_AIE", &[], &[4], &[0u8; 4], &[0u8; 4]);
199 assert!(matches!(
200 PrecompiledArtifact::parse(&no_inputs),
201 Err(BackendError::InvalidArgument)
202 ));
203 let zero_slot = encode("MLIR_AIE", &[0], &[4], &[0u8; 4], &[0u8; 4]);
204 assert!(matches!(
205 PrecompiledArtifact::parse(&zero_slot),
206 Err(BackendError::InvalidArgument)
207 ));
208 }
209
210 #[test]
211 fn rejects_truncated_container() {
212 let bytes = encode("MLIR_AIE", &[40], &[8], &[0xAB; 40], &[0x11; 8]);
213 assert!(matches!(
214 PrecompiledArtifact::parse(&bytes[..bytes.len() - 4]),
215 Err(BackendError::InvalidArgument)
216 ));
217 }
218}