Skip to main content

virtio_accel_mock/
reference.rs

1//! Test-only artifact format understood by [`crate::MockAccelerator`].
2//!
3//! This format exists solely to make the reference backend's execution observable and
4//! deterministic. It is not part of the virtio-accel ABI, and production transports must continue
5//! to treat provider artifacts as opaque bytes.
6
7use virtio_accel_core::{ArtifactFormat, BackendError, ByteSource, TargetIdentity};
8
9const MAGIC: [u8; 4] = *b"VAMK";
10const VERSION_MAJOR: u8 = 1;
11const VERSION_MINOR: u8 = 0;
12const BINDING_ABI_VERSION: u8 = 1;
13const UNUSED_SLOT: u32 = u32::MAX;
14
15const OP_BARRIER: u8 = 0;
16const OP_COPY: u8 = 1;
17const OP_FILL: u8 = 2;
18const OP_XOR: u8 = 3;
19
20/// Provider-owned format ID reserved by the reference backend for its test artifact.
21pub const ARTIFACT_FORMAT: ArtifactFormat = match ArtifactFormat::new(0x5641_4d4b) {
22    Some(format) => format,
23    None => panic!("reference artifact format must be nonzero"),
24};
25
26/// Provider-owned target identity for the reference execution model.
27pub const TARGET_IDENTITY: TargetIdentity = TargetIdentity([
28    0x5641_4d4b,
29    0x0001_0000,
30    0x5445_5354,
31    0x4f4e_4c59,
32    0,
33    0,
34    0,
35    0,
36    0,
37    0,
38    0,
39    0,
40]);
41
42/// Exact encoded payload size for every reference artifact.
43pub const ARTIFACT_BYTES: usize = 24;
44
45/// Exact resident charge required by the reference backend.
46pub const RESIDENT_BYTES: u64 = 64;
47
48/// A validated reference operation embedded in a test artifact.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub(crate) enum Operation {
51    Barrier { slot: u32 },
52    Copy { source_slot: u32, target_slot: u32 },
53    Fill { target_slot: u32, value: u8 },
54    Xor { target_slot: u32, value: u8 },
55}
56
57/// Fixed-size builder for the mock backend's test-only executable format.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct ReferenceArtifact {
60    bytes: [u8; ARTIFACT_BYTES],
61}
62
63impl ReferenceArtifact {
64    /// Build a no-op submission used to exercise lifecycle and synchronization behavior.
65    ///
66    /// The operation requires exactly one binding at `slot`, with any otherwise-valid access.
67    pub fn barrier(slot: u32) -> Self {
68        Self::new(OP_BARRIER, slot, UNUSED_SLOT, 0)
69    }
70
71    /// Build a byte-for-byte copy from a `Read` binding to an equal-length `Write` binding.
72    pub fn copy(source_slot: u32, target_slot: u32) -> Result<Self, BackendError> {
73        if source_slot == target_slot {
74            return Err(BackendError::InvalidArgument);
75        }
76        Ok(Self::new(OP_COPY, source_slot, target_slot, 0))
77    }
78
79    /// Build a fill of the complete `Write` range bound at `target_slot`.
80    pub fn fill(target_slot: u32, value: u8) -> Self {
81        Self::new(OP_FILL, target_slot, UNUSED_SLOT, value)
82    }
83
84    /// Build an in-place XOR of the complete `ReadWrite` range bound at `target_slot`.
85    pub fn xor(target_slot: u32, value: u8) -> Self {
86        Self::new(OP_XOR, target_slot, UNUSED_SLOT, value)
87    }
88
89    pub const fn as_bytes(&self) -> &[u8; ARTIFACT_BYTES] {
90        &self.bytes
91    }
92
93    fn new(opcode: u8, first_slot: u32, second_slot: u32, operand: u8) -> Self {
94        let mut bytes = [0; ARTIFACT_BYTES];
95        bytes[0..4].copy_from_slice(&MAGIC);
96        bytes[4] = VERSION_MAJOR;
97        bytes[5] = VERSION_MINOR;
98        bytes[6] = opcode;
99        bytes[7] = BINDING_ABI_VERSION;
100        bytes[8..12].copy_from_slice(&first_slot.to_le_bytes());
101        bytes[12..16].copy_from_slice(&second_slot.to_le_bytes());
102        bytes[16] = operand;
103        Self { bytes }
104    }
105}
106
107impl AsRef<[u8]> for ReferenceArtifact {
108    fn as_ref(&self) -> &[u8] {
109        self.as_bytes()
110    }
111}
112
113pub(crate) fn decode(payload: &dyn ByteSource) -> Result<Operation, BackendError> {
114    if payload.len() != ARTIFACT_BYTES as u64 {
115        return Err(BackendError::InvalidArgument);
116    }
117
118    let mut bytes = [0; ARTIFACT_BYTES];
119    payload.read_at(0, &mut bytes)?;
120    if bytes[0..4] != MAGIC {
121        return Err(BackendError::InvalidArgument);
122    }
123    if bytes[4] != VERSION_MAJOR || bytes[5] != VERSION_MINOR || bytes[7] != BINDING_ABI_VERSION {
124        return Err(BackendError::Incompatible);
125    }
126    if bytes[17..].iter().any(|byte| *byte != 0) {
127        return Err(BackendError::InvalidArgument);
128    }
129
130    let first_slot = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
131    let second_slot = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
132    let operand = bytes[16];
133    match bytes[6] {
134        OP_BARRIER if second_slot == UNUSED_SLOT && operand == 0 => {
135            Ok(Operation::Barrier { slot: first_slot })
136        }
137        OP_COPY if first_slot != second_slot && operand == 0 => Ok(Operation::Copy {
138            source_slot: first_slot,
139            target_slot: second_slot,
140        }),
141        OP_FILL if second_slot == UNUSED_SLOT => Ok(Operation::Fill {
142            target_slot: first_slot,
143            value: operand,
144        }),
145        OP_XOR if second_slot == UNUSED_SLOT => Ok(Operation::Xor {
146            target_slot: first_slot,
147            value: operand,
148        }),
149        OP_BARRIER | OP_COPY | OP_FILL | OP_XOR => Err(BackendError::InvalidArgument),
150        _ => Err(BackendError::Unsupported),
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn builders_round_trip_through_the_fixed_envelope() {
160        for (artifact, expected) in [
161            (
162                ReferenceArtifact::barrier(9),
163                Operation::Barrier { slot: 9 },
164            ),
165            (
166                ReferenceArtifact::copy(2, 7).unwrap(),
167                Operation::Copy {
168                    source_slot: 2,
169                    target_slot: 7,
170                },
171            ),
172            (
173                ReferenceArtifact::fill(3, 0xa5),
174                Operation::Fill {
175                    target_slot: 3,
176                    value: 0xa5,
177                },
178            ),
179            (
180                ReferenceArtifact::xor(4, 0x5a),
181                Operation::Xor {
182                    target_slot: 4,
183                    value: 0x5a,
184                },
185            ),
186        ] {
187            assert_eq!(decode(artifact.as_bytes()), Ok(expected));
188        }
189        assert_eq!(
190            ReferenceArtifact::copy(2, 2),
191            Err(BackendError::InvalidArgument)
192        );
193    }
194
195    #[test]
196    fn malformed_and_incompatible_envelopes_are_distinguished() {
197        let mut malformed = ReferenceArtifact::barrier(0).bytes;
198        malformed[17] = 1;
199        assert_eq!(decode(&malformed), Err(BackendError::InvalidArgument));
200
201        let mut incompatible = ReferenceArtifact::barrier(0).bytes;
202        incompatible[4] += 1;
203        assert_eq!(decode(&incompatible), Err(BackendError::Incompatible));
204
205        let mut unsupported = ReferenceArtifact::barrier(0).bytes;
206        unsupported[6] = 0xff;
207        assert_eq!(decode(&unsupported), Err(BackendError::Unsupported));
208    }
209}