Skip to main content

virtio_accel_mock/
lib.rs

1//! In-memory reference implementation of the portable accelerator lifecycle.
2//!
3//! This backend is intentionally synchronous internally, but submissions remain pending until
4//! [`MockAccelerator::complete`] is called so transports can test in-flight ownership.
5
6#![forbid(unsafe_code)]
7
8pub mod fault;
9pub mod reference;
10
11use core::ops::Range;
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
14use std::vec::Vec;
15use virtio_accel_core::{
16    Accelerator, AcceleratorClass, AllocatedBuffer, ArtifactFormat, ArtifactRef, BackendError,
17    BindingRef, BufferDesc, BufferInfo, BufferProperties, ByteSink, ByteSource, Capabilities,
18    ContextDesc, DeviceIdentity, DeviceInfo, DeviceLimits, EventState, MemoryDomain, QueueDesc,
19    ReleaseFailure, SubmitFailure, TargetIdentity, Timeout,
20};
21
22use reference::Operation;
23
24const EVENT_PENDING: u8 = 0;
25const EVENT_EXECUTING: u8 = 1;
26const EVENT_COMPLETE: u8 = 2;
27const EVENT_CANCELLED: u8 = 3;
28const EVENT_DEVICE_LOST: u8 = 4;
29const TRANSFER_CHUNK_BYTES: usize = 256;
30
31#[derive(Clone, Debug)]
32pub struct MockContext {
33    id: u64,
34}
35
36#[derive(Debug)]
37pub struct MockBuffer {
38    context_id: u64,
39    desc: BufferDesc,
40    data: Arc<[AtomicU8]>,
41}
42
43#[derive(Clone, Debug)]
44pub struct MockProgram {
45    context_id: u64,
46    format: ArtifactFormat,
47    target: TargetIdentity,
48    payload_bytes: usize,
49    operation: Operation,
50}
51
52impl MockProgram {
53    pub const fn format(&self) -> ArtifactFormat {
54        self.format
55    }
56
57    pub const fn target(&self) -> TargetIdentity {
58        self.target
59    }
60
61    pub const fn payload_bytes(&self) -> usize {
62        self.payload_bytes
63    }
64}
65
66#[derive(Clone, Debug)]
67pub struct MockQueue {
68    context_id: u64,
69}
70
71#[derive(Clone, Debug)]
72pub struct MockEvent {
73    inner: Arc<MockEventInner>,
74}
75
76#[derive(Debug)]
77struct MockEventInner {
78    state: AtomicU8,
79    invocation: MockInvocation,
80}
81
82#[derive(Clone, Debug)]
83struct BufferSlice {
84    data: Arc<[AtomicU8]>,
85    range: Range<usize>,
86}
87
88#[derive(Debug)]
89enum MockInvocation {
90    Barrier,
91    Copy {
92        source: BufferSlice,
93        target: BufferSlice,
94    },
95    Fill {
96        target: BufferSlice,
97        value: u8,
98    },
99    Xor {
100        target: BufferSlice,
101        value: u8,
102    },
103}
104
105impl MockInvocation {
106    fn execute(&self) {
107        match self {
108            Self::Barrier => {}
109            Self::Copy { source, target } => {
110                let reverse = Arc::ptr_eq(&source.data, &target.data)
111                    && target.range.start > source.range.start
112                    && target.range.start < source.range.end;
113                if reverse {
114                    for index in (0..source.range.len()).rev() {
115                        let byte = source.data[source.range.start + index].load(Ordering::Relaxed);
116                        target.data[target.range.start + index].store(byte, Ordering::Relaxed);
117                    }
118                } else {
119                    for index in 0..source.range.len() {
120                        let byte = source.data[source.range.start + index].load(Ordering::Relaxed);
121                        target.data[target.range.start + index].store(byte, Ordering::Relaxed);
122                    }
123                }
124            }
125            Self::Fill { target, value } => {
126                for byte in &target.data[target.range.clone()] {
127                    byte.store(*value, Ordering::Relaxed);
128                }
129            }
130            Self::Xor { target, value } => {
131                for byte in &target.data[target.range.clone()] {
132                    byte.fetch_xor(*value, Ordering::Relaxed);
133                }
134            }
135        }
136    }
137}
138
139pub struct MockAccelerator {
140    next_id: AtomicU64,
141    direct_binding_admissions: AtomicU64,
142    info: DeviceInfo,
143}
144
145impl Default for MockAccelerator {
146    fn default() -> Self {
147        Self {
148            next_id: AtomicU64::new(1),
149            direct_binding_admissions: AtomicU64::new(0),
150            info: DeviceInfo {
151                identity: DeviceIdentity {
152                    uuid: *b"virtio-accelmock",
153                    class: AcceleratorClass::NPU,
154                    vendor_id: 0,
155                    device_id: 0,
156                },
157                capabilities: Capabilities::HOST_VISIBLE_MEMORY
158                    | Capabilities::DEVICE_LOCAL_MEMORY
159                    | Capabilities::SHARED_MEMORY
160                    | Capabilities::EVENT_CANCELLATION,
161                limits: DeviceLimits {
162                    max_contexts: 64,
163                    max_buffers_per_context: 1_024,
164                    max_programs_per_context: 256,
165                    max_queues_per_context: 16,
166                    max_events_per_context: 4_096,
167                    max_bindings_per_submission: 256,
168                    max_buffer_bytes: 1 << 30,
169                    max_artifact_bytes: 1 << 30,
170                },
171            },
172        }
173    }
174}
175
176impl MockAccelerator {
177    /// Cumulative provider-owned bindings admitted without hidden submission staging.
178    pub fn direct_binding_admissions(&self) -> u64 {
179        self.direct_binding_admissions.load(Ordering::Relaxed)
180    }
181
182    pub fn complete(&self, event: &MockEvent) -> Result<(), BackendError> {
183        event
184            .inner
185            .state
186            .compare_exchange(
187                EVENT_PENDING,
188                EVENT_EXECUTING,
189                Ordering::AcqRel,
190                Ordering::Acquire,
191            )
192            .map_err(|_| BackendError::Busy)?;
193        event.inner.invocation.execute();
194        event.inner.state.store(EVENT_COMPLETE, Ordering::Release);
195        Ok(())
196    }
197
198    /// Fail a pending event before execution, simulating harness-controlled device loss.
199    pub fn fail_device_lost(&self, event: &MockEvent) -> Result<(), BackendError> {
200        event
201            .inner
202            .state
203            .compare_exchange(
204                EVENT_PENDING,
205                EVENT_DEVICE_LOST,
206                Ordering::AcqRel,
207                Ordering::Acquire,
208            )
209            .map(|_| ())
210            .map_err(|_| BackendError::Busy)
211    }
212
213    fn next_id(&self) -> Result<u64, BackendError> {
214        self.next_id
215            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
216                current.checked_add(1)
217            })
218            .map_err(|_| BackendError::ResourceLimit)
219    }
220
221    fn checked_range(
222        total: usize,
223        offset: u64,
224        bytes: usize,
225    ) -> Result<core::ops::Range<usize>, BackendError> {
226        let start = usize::try_from(offset).map_err(|_| BackendError::OutOfBounds)?;
227        let end = start
228            .checked_add(bytes)
229            .filter(|end| *end <= total)
230            .ok_or(BackendError::OutOfBounds)?;
231        Ok(start..end)
232    }
233
234    fn binding_for_slot<'slice, 'buffer>(
235        bindings: &'slice [BindingRef<'buffer, MockBuffer>],
236        slot: u32,
237    ) -> Option<&'slice BindingRef<'buffer, MockBuffer>> {
238        bindings.iter().find(|binding| binding.slot == slot)
239    }
240
241    fn buffer_slice(binding: &BindingRef<'_, MockBuffer>) -> Result<BufferSlice, BackendError> {
242        let bytes =
243            usize::try_from(binding.range.bytes()).map_err(|_| BackendError::OutOfBounds)?;
244        Ok(BufferSlice {
245            data: Arc::clone(&binding.buffer.data),
246            range: Self::checked_range(binding.buffer.data.len(), binding.range.offset, bytes)?,
247        })
248    }
249
250    fn prepare_invocation(
251        operation: Operation,
252        bindings: &[BindingRef<'_, MockBuffer>],
253    ) -> Result<MockInvocation, BackendError> {
254        let incompatible = || BackendError::Incompatible;
255        match operation {
256            Operation::Barrier { slot } => {
257                if bindings.len() != 1 || Self::binding_for_slot(bindings, slot).is_none() {
258                    return Err(incompatible());
259                }
260                Ok(MockInvocation::Barrier)
261            }
262            Operation::Copy {
263                source_slot,
264                target_slot,
265            } => {
266                if bindings.len() != 2 {
267                    return Err(incompatible());
268                }
269                let source = Self::binding_for_slot(bindings, source_slot)
270                    .filter(|binding| binding.access == virtio_accel_core::AccessMode::Read)
271                    .ok_or_else(incompatible)?;
272                let target = Self::binding_for_slot(bindings, target_slot)
273                    .filter(|binding| binding.access == virtio_accel_core::AccessMode::Write)
274                    .ok_or_else(incompatible)?;
275                if source.range.bytes() != target.range.bytes() {
276                    return Err(incompatible());
277                }
278                Ok(MockInvocation::Copy {
279                    source: Self::buffer_slice(source)?,
280                    target: Self::buffer_slice(target)?,
281                })
282            }
283            Operation::Fill { target_slot, value } => {
284                if bindings.len() != 1 {
285                    return Err(incompatible());
286                }
287                let target = Self::binding_for_slot(bindings, target_slot)
288                    .filter(|binding| binding.access == virtio_accel_core::AccessMode::Write)
289                    .ok_or_else(incompatible)?;
290                Ok(MockInvocation::Fill {
291                    target: Self::buffer_slice(target)?,
292                    value,
293                })
294            }
295            Operation::Xor { target_slot, value } => {
296                if bindings.len() != 1 {
297                    return Err(incompatible());
298                }
299                let target = Self::binding_for_slot(bindings, target_slot)
300                    .filter(|binding| binding.access == virtio_accel_core::AccessMode::ReadWrite)
301                    .ok_or_else(incompatible)?;
302                Ok(MockInvocation::Xor {
303                    target: Self::buffer_slice(target)?,
304                    value,
305                })
306            }
307        }
308    }
309}
310
311impl Accelerator for MockAccelerator {
312    type Context = MockContext;
313    type Buffer = MockBuffer;
314    type Program = MockProgram;
315    type Queue = MockQueue;
316    type Event = MockEvent;
317
318    fn device_info(&self) -> Result<DeviceInfo, BackendError> {
319        Ok(self.info)
320    }
321
322    fn create_context(&self, desc: ContextDesc) -> Result<Self::Context, BackendError> {
323        self.info.validate_context_desc(desc)?;
324        Ok(MockContext {
325            id: self.next_id()?,
326        })
327    }
328
329    fn destroy_context(
330        &self,
331        _context: Self::Context,
332    ) -> Result<(), ReleaseFailure<Self::Context>> {
333        Ok(())
334    }
335
336    fn allocate_buffer(
337        &self,
338        context: &Self::Context,
339        desc: BufferDesc,
340    ) -> Result<AllocatedBuffer<Self::Buffer>, BackendError> {
341        self.info.validate_buffer_desc(desc)?;
342        let properties = match desc.domain {
343            MemoryDomain::Host => BufferProperties::HOST_VISIBLE,
344            MemoryDomain::Device => BufferProperties::DEVICE_LOCAL,
345            MemoryDomain::Shared => BufferProperties::HOST_VISIBLE,
346        } | if desc.is_program_visible() || desc.domain == MemoryDomain::Shared {
347            BufferProperties::DIRECT_BINDING
348        } else {
349            BufferProperties::empty()
350        };
351        let info = BufferInfo::new(desc, desc.bytes(), desc.alignment(), properties)?;
352        let bytes = usize::try_from(desc.bytes()).map_err(|_| BackendError::OutOfMemory)?;
353        let mut data = Vec::new();
354        data.try_reserve_exact(bytes)
355            .map_err(|_| BackendError::OutOfMemory)?;
356        data.resize_with(bytes, || AtomicU8::new(0));
357        Ok(AllocatedBuffer::new(
358            MockBuffer {
359                context_id: context.id,
360                desc,
361                data: Arc::from(data.into_boxed_slice()),
362            },
363            info,
364        ))
365    }
366
367    fn write_buffer(
368        &self,
369        buffer: &mut Self::Buffer,
370        offset: u64,
371        data: &dyn ByteSource,
372    ) -> Result<(), BackendError> {
373        if !buffer
374            .desc
375            .usage
376            .contains(virtio_accel_core::BufferUsage::TRANSFER_DESTINATION)
377        {
378            return Err(BackendError::PermissionDenied);
379        }
380        let bytes = usize::try_from(data.len()).map_err(|_| BackendError::OutOfBounds)?;
381        if bytes == 0 {
382            return Err(BackendError::InvalidArgument);
383        }
384        let range = Self::checked_range(buffer.data.len(), offset, bytes)?;
385        if let Some(source) = data.as_contiguous() {
386            if source.len() != bytes {
387                return Err(BackendError::InvalidArgument);
388            }
389            for (target, source) in buffer.data[range].iter().zip(source) {
390                target.store(*source, Ordering::Relaxed);
391            }
392        } else {
393            let mut scratch = [0; TRANSFER_CHUNK_BYTES];
394            let mut copied = 0;
395            while copied < bytes {
396                let chunk_bytes = (bytes - copied).min(TRANSFER_CHUNK_BYTES);
397                data.read_at(copied as u64, &mut scratch[..chunk_bytes])?;
398                for (target, source) in buffer.data[range.start + copied..][..chunk_bytes]
399                    .iter()
400                    .zip(&scratch[..chunk_bytes])
401                {
402                    target.store(*source, Ordering::Relaxed);
403                }
404                copied += chunk_bytes;
405            }
406        }
407        Ok(())
408    }
409
410    fn read_buffer(
411        &self,
412        buffer: &Self::Buffer,
413        offset: u64,
414        data: &mut dyn ByteSink,
415    ) -> Result<(), BackendError> {
416        if !buffer
417            .desc
418            .usage
419            .contains(virtio_accel_core::BufferUsage::TRANSFER_SOURCE)
420        {
421            return Err(BackendError::PermissionDenied);
422        }
423        let bytes = usize::try_from(data.len()).map_err(|_| BackendError::OutOfBounds)?;
424        if bytes == 0 {
425            return Err(BackendError::InvalidArgument);
426        }
427        let range = Self::checked_range(buffer.data.len(), offset, bytes)?;
428        if let Some(target) = data.as_contiguous_mut() {
429            if target.len() != bytes {
430                return Err(BackendError::InvalidArgument);
431            }
432            for (target, source) in target.iter_mut().zip(&buffer.data[range]) {
433                *target = source.load(Ordering::Relaxed);
434            }
435        } else {
436            let mut scratch = [0; TRANSFER_CHUNK_BYTES];
437            let mut copied = 0;
438            while copied < bytes {
439                let chunk_bytes = (bytes - copied).min(TRANSFER_CHUNK_BYTES);
440                for (target, source) in scratch[..chunk_bytes]
441                    .iter_mut()
442                    .zip(&buffer.data[range.start + copied..][..chunk_bytes])
443                {
444                    *target = source.load(Ordering::Relaxed);
445                }
446                data.write_at(copied as u64, &scratch[..chunk_bytes])?;
447                copied += chunk_bytes;
448            }
449        }
450        Ok(())
451    }
452
453    fn free_buffer(&self, _buffer: Self::Buffer) -> Result<(), ReleaseFailure<Self::Buffer>> {
454        Ok(())
455    }
456
457    fn load_program(
458        &self,
459        context: &Self::Context,
460        artifact: ArtifactRef<'_>,
461    ) -> Result<Self::Program, BackendError> {
462        if artifact.payload.len() > self.info.limits.max_artifact_bytes {
463            return Err(BackendError::ResourceLimit);
464        }
465        if artifact.format != reference::ARTIFACT_FORMAT {
466            return Err(BackendError::Unsupported);
467        }
468        if artifact.target != reference::TARGET_IDENTITY
469            || artifact.resident_bytes != reference::RESIDENT_BYTES
470        {
471            return Err(BackendError::Incompatible);
472        }
473        let operation = reference::decode(artifact.payload)?;
474        let payload_bytes =
475            usize::try_from(artifact.payload.len()).map_err(|_| BackendError::ResourceLimit)?;
476        Ok(MockProgram {
477            context_id: context.id,
478            format: artifact.format,
479            target: artifact.target,
480            payload_bytes,
481            operation,
482        })
483    }
484
485    fn unload_program(&self, _program: Self::Program) -> Result<(), ReleaseFailure<Self::Program>> {
486        Ok(())
487    }
488
489    fn create_queue(
490        &self,
491        context: &Self::Context,
492        desc: QueueDesc,
493    ) -> Result<Self::Queue, BackendError> {
494        self.info.validate_queue_desc(desc)?;
495        Ok(MockQueue {
496            context_id: context.id,
497        })
498    }
499
500    fn destroy_queue(&self, _queue: Self::Queue) -> Result<(), ReleaseFailure<Self::Queue>> {
501        Ok(())
502    }
503
504    fn submit(
505        &self,
506        queue: &Self::Queue,
507        program: &Self::Program,
508        bindings: &[BindingRef<'_, Self::Buffer>],
509        _timeout: Timeout,
510    ) -> Result<Self::Event, SubmitFailure<Self::Event>> {
511        // Provider-side defense-in-depth. Hosts must still call
512        // `BindingRef::validate_for_submit` before `Accelerator::submit`
513        // (as `CommandProcessor` does). Range checks run before the shared usage
514        // gate so overlapping faults prefer `OutOfBounds` over `PermissionDenied`.
515        if queue.context_id != program.context_id
516            || bindings
517                .iter()
518                .any(|binding| binding.buffer.context_id != queue.context_id)
519        {
520            return Err(SubmitFailure::Rejected(BackendError::InvalidArgument));
521        }
522        for binding in bindings {
523            if binding.range.end() > binding.buffer.desc.bytes() {
524                return Err(SubmitFailure::Rejected(BackendError::OutOfBounds));
525            }
526        }
527        let descs: Vec<BufferDesc> = bindings.iter().map(|binding| binding.buffer.desc).collect();
528        BindingRef::validate_for_submit(
529            bindings,
530            &descs,
531            self.info.limits.max_bindings_per_submission,
532        )
533        .map_err(SubmitFailure::Rejected)?;
534        let invocation = Self::prepare_invocation(program.operation, bindings)
535            .map_err(SubmitFailure::Rejected)?;
536        self.direct_binding_admissions
537            .fetch_add(bindings.len() as u64, Ordering::Relaxed);
538        Ok(MockEvent {
539            inner: Arc::new(MockEventInner {
540                state: AtomicU8::new(EVENT_PENDING),
541                invocation,
542            }),
543        })
544    }
545
546    fn poll_event(&self, event: &Self::Event) -> Result<EventState, BackendError> {
547        match event.inner.state.load(Ordering::Acquire) {
548            EVENT_PENDING | EVENT_EXECUTING => Ok(EventState::Pending),
549            EVENT_COMPLETE => Ok(EventState::Complete),
550            EVENT_CANCELLED => Ok(EventState::Cancelled),
551            EVENT_DEVICE_LOST => Ok(EventState::Failed(BackendError::DeviceLost)),
552            _ => Err(BackendError::DeviceLost),
553        }
554    }
555
556    fn cancel_event(&self, event: &Self::Event) -> Result<(), BackendError> {
557        event
558            .inner
559            .state
560            .compare_exchange(
561                EVENT_PENDING,
562                EVENT_CANCELLED,
563                Ordering::AcqRel,
564                Ordering::Acquire,
565            )
566            .map(|_| ())
567            .map_err(|_| BackendError::Busy)
568    }
569
570    fn destroy_event(&self, event: Self::Event) -> Result<(), ReleaseFailure<Self::Event>> {
571        let state = self
572            .poll_event(&event)
573            .map_err(|error| ReleaseFailure::Indeterminate { error })?;
574        match state {
575            EventState::Pending => Err(ReleaseFailure::Rejected {
576                error: BackendError::Busy,
577                resource: event,
578            }),
579            EventState::Complete | EventState::Failed(_) | EventState::Cancelled => Ok(()),
580        }
581    }
582}
583
584#[cfg(test)]
585mod tests {
586    use super::*;
587    use virtio_accel_core::{
588        AccessMode, BindingRef, BufferRange, BufferUsage, ContextFlags, QueueFlags,
589    };
590
591    fn load_reference(
592        backend: &MockAccelerator,
593        context: &MockContext,
594        artifact: &reference::ReferenceArtifact,
595    ) -> MockProgram {
596        backend
597            .load_program(
598                context,
599                ArtifactRef {
600                    format: reference::ARTIFACT_FORMAT,
601                    target: reference::TARGET_IDENTITY,
602                    payload: artifact.as_bytes(),
603                    resident_bytes: reference::RESIDENT_BYTES,
604                },
605            )
606            .unwrap()
607    }
608
609    #[derive(Debug)]
610    struct SplitSource<'a> {
611        first: &'a [u8],
612        second: &'a [u8],
613    }
614
615    impl ByteSource for SplitSource<'_> {
616        fn len(&self) -> u64 {
617            (self.first.len() + self.second.len()) as u64
618        }
619
620        fn read_at(&self, offset: u64, target: &mut [u8]) -> Result<(), BackendError> {
621            let start = usize::try_from(offset).map_err(|_| BackendError::OutOfBounds)?;
622            let end = start
623                .checked_add(target.len())
624                .filter(|end| *end <= self.first.len() + self.second.len())
625                .ok_or(BackendError::OutOfBounds)?;
626            for (segment_start, segment) in [(0, self.first), (self.first.len(), self.second)] {
627                let overlap_start = start.max(segment_start);
628                let overlap_end = end.min(segment_start + segment.len());
629                if overlap_start < overlap_end {
630                    target[overlap_start - start..overlap_end - start].copy_from_slice(
631                        &segment[overlap_start - segment_start..overlap_end - segment_start],
632                    );
633                }
634            }
635            Ok(())
636        }
637    }
638
639    #[derive(Debug)]
640    struct SplitSink<'a> {
641        first: &'a mut [u8],
642        second: &'a mut [u8],
643    }
644
645    impl ByteSink for SplitSink<'_> {
646        fn len(&self) -> u64 {
647            (self.first.len() + self.second.len()) as u64
648        }
649
650        fn write_at(&mut self, offset: u64, source: &[u8]) -> Result<(), BackendError> {
651            let start = usize::try_from(offset).map_err(|_| BackendError::OutOfBounds)?;
652            let end = start
653                .checked_add(source.len())
654                .filter(|end| *end <= self.first.len() + self.second.len())
655                .ok_or(BackendError::OutOfBounds)?;
656            let first_len = self.first.len();
657            for (segment_start, segment) in [(0, &mut *self.first), (first_len, &mut *self.second)]
658            {
659                let overlap_start = start.max(segment_start);
660                let overlap_end = end.min(segment_start + segment.len());
661                if overlap_start < overlap_end {
662                    segment[overlap_start - segment_start..overlap_end - segment_start]
663                        .copy_from_slice(&source[overlap_start - start..overlap_end - start]);
664                }
665            }
666            Ok(())
667        }
668    }
669
670    #[test]
671    fn reference_backend_exercises_the_complete_lifecycle() {
672        let backend = MockAccelerator::default();
673        let context = backend.create_context(ContextDesc::default()).unwrap();
674        let desc = BufferDesc::new(
675            16,
676            8,
677            MemoryDomain::Shared,
678            BufferUsage::TRANSFER_SOURCE
679                | BufferUsage::TRANSFER_DESTINATION
680                | BufferUsage::PROGRAM_INPUT,
681        )
682        .unwrap();
683        let allocation = backend.allocate_buffer(&context, desc).unwrap();
684        assert_eq!(allocation.info().desc(), desc);
685        assert_eq!(allocation.info().allocation_bytes(), 16);
686        assert_eq!(allocation.info().alignment(), 8);
687        assert!(
688            allocation
689                .info()
690                .properties()
691                .contains(BufferProperties::DIRECT_BINDING)
692        );
693        let (mut buffer, _) = allocation.into_parts();
694        backend.write_buffer(&mut buffer, 4, &[1, 2, 3, 4]).unwrap();
695        let mut output = [0; 4];
696        backend.read_buffer(&buffer, 4, &mut output).unwrap();
697        assert_eq!(output, [1, 2, 3, 4]);
698
699        let artifact = reference::ReferenceArtifact::barrier(0);
700        let program = load_reference(&backend, &context, &artifact);
701        assert_eq!(program.format(), reference::ARTIFACT_FORMAT);
702        assert_eq!(program.target(), reference::TARGET_IDENTITY);
703        assert_eq!(program.payload_bytes(), reference::ARTIFACT_BYTES);
704        let queue = backend
705            .create_queue(&context, QueueDesc::default())
706            .unwrap();
707        let bindings = [BindingRef {
708            slot: 0,
709            buffer: &buffer,
710            range: BufferRange::new(0, 16).unwrap(),
711            access: AccessMode::Read,
712        }];
713        let event = backend
714            .submit(&queue, &program, &bindings, Timeout::Infinite)
715            .unwrap();
716        assert_eq!(backend.poll_event(&event), Ok(EventState::Pending));
717        assert!(matches!(
718            backend.destroy_event(event.clone()),
719            Err(ReleaseFailure::Rejected {
720                error: BackendError::Busy,
721                ..
722            })
723        ));
724        backend.complete(&event).unwrap();
725        assert_eq!(backend.poll_event(&event), Ok(EventState::Complete));
726
727        backend.destroy_event(event).unwrap();
728        backend.destroy_queue(queue).unwrap();
729        backend.unload_program(program).unwrap();
730        backend.free_buffer(buffer).unwrap();
731        backend.destroy_context(context).unwrap();
732    }
733
734    #[test]
735    fn explicit_transfers_enforce_declared_direction() {
736        let backend = MockAccelerator::default();
737        let context = backend.create_context(ContextDesc::default()).unwrap();
738        let allocation = backend
739            .allocate_buffer(
740                &context,
741                BufferDesc::new(4, 1, MemoryDomain::Host, BufferUsage::TRANSFER_SOURCE).unwrap(),
742            )
743            .unwrap();
744        let (mut buffer, _) = allocation.into_parts();
745        assert_eq!(
746            backend.write_buffer(&mut buffer, 0, &[1]),
747            Err(BackendError::PermissionDenied)
748        );
749
750        backend.free_buffer(buffer).unwrap();
751        backend.destroy_context(context).unwrap();
752    }
753
754    #[test]
755    fn reserved_creation_flags_are_rejected_without_resources() {
756        let backend = MockAccelerator::default();
757        assert!(matches!(
758            backend.create_context(ContextDesc {
759                flags: ContextFlags::SECURE,
760            }),
761            Err(BackendError::Unsupported)
762        ));
763
764        let context = backend.create_context(ContextDesc::default()).unwrap();
765        assert!(matches!(
766            backend.create_queue(
767                &context,
768                QueueDesc {
769                    flags: QueueFlags::IN_ORDER,
770                },
771            ),
772            Err(BackendError::Unsupported)
773        ));
774        backend.destroy_context(context).unwrap();
775    }
776
777    #[test]
778    fn segmented_transfers_do_not_require_coalescing() {
779        let backend = MockAccelerator::default();
780        let context = backend.create_context(ContextDesc::default()).unwrap();
781        let allocation = backend
782            .allocate_buffer(
783                &context,
784                BufferDesc::new(
785                    4,
786                    1,
787                    MemoryDomain::Host,
788                    BufferUsage::TRANSFER_SOURCE | BufferUsage::TRANSFER_DESTINATION,
789                )
790                .unwrap(),
791            )
792            .unwrap();
793        let (mut buffer, _) = allocation.into_parts();
794
795        let source = SplitSource {
796            first: &[1, 2],
797            second: &[3, 4],
798        };
799        backend.write_buffer(&mut buffer, 0, &source).unwrap();
800
801        let mut first = [0; 1];
802        let mut second = [0; 3];
803        let mut sink = SplitSink {
804            first: &mut first,
805            second: &mut second,
806        };
807        backend.read_buffer(&buffer, 0, &mut sink).unwrap();
808        assert_eq!(first, [1]);
809        assert_eq!(second, [2, 3, 4]);
810
811        backend.free_buffer(buffer).unwrap();
812        backend.destroy_context(context).unwrap();
813    }
814
815    #[test]
816    fn cross_context_submission_is_rejected_before_acceptance() {
817        let backend = MockAccelerator::default();
818        let context_a = backend.create_context(ContextDesc::default()).unwrap();
819        let context_b = backend.create_context(ContextDesc::default()).unwrap();
820        let allocation = backend
821            .allocate_buffer(
822                &context_b,
823                BufferDesc::new(1, 1, MemoryDomain::Host, BufferUsage::PROGRAM_INPUT).unwrap(),
824            )
825            .unwrap();
826        let (buffer, _) = allocation.into_parts();
827        let artifact = reference::ReferenceArtifact::barrier(0);
828        let program = load_reference(&backend, &context_a, &artifact);
829        let queue = backend
830            .create_queue(&context_a, QueueDesc::default())
831            .unwrap();
832        let bindings = [BindingRef {
833            slot: 0,
834            buffer: &buffer,
835            range: BufferRange::new(0, 1).unwrap(),
836            access: AccessMode::Read,
837        }];
838        assert!(matches!(
839            backend.submit(&queue, &program, &bindings, Timeout::Infinite),
840            Err(SubmitFailure::Rejected(BackendError::InvalidArgument))
841        ));
842    }
843
844    #[test]
845    fn copy_produces_verifiable_output_only_after_completion() {
846        let backend = MockAccelerator::default();
847        let context = backend.create_context(ContextDesc::default()).unwrap();
848        let source = backend
849            .allocate_buffer(
850                &context,
851                BufferDesc::new(
852                    8,
853                    1,
854                    MemoryDomain::Host,
855                    BufferUsage::TRANSFER_DESTINATION | BufferUsage::PROGRAM_INPUT,
856                )
857                .unwrap(),
858            )
859            .unwrap();
860        let target = backend
861            .allocate_buffer(
862                &context,
863                BufferDesc::new(
864                    8,
865                    1,
866                    MemoryDomain::Host,
867                    BufferUsage::TRANSFER_SOURCE | BufferUsage::PROGRAM_OUTPUT,
868                )
869                .unwrap(),
870            )
871            .unwrap();
872        let (mut source, _) = source.into_parts();
873        let (target, _) = target.into_parts();
874        backend.write_buffer(&mut source, 0, b"copy me").unwrap();
875
876        let artifact = reference::ReferenceArtifact::copy(3, 7).unwrap();
877        let program = load_reference(&backend, &context, &artifact);
878        let queue = backend
879            .create_queue(&context, QueueDesc::default())
880            .unwrap();
881        let bindings = [
882            BindingRef {
883                slot: 7,
884                buffer: &target,
885                range: BufferRange::new(0, 7).unwrap(),
886                access: AccessMode::Write,
887            },
888            BindingRef {
889                slot: 3,
890                buffer: &source,
891                range: BufferRange::new(0, 7).unwrap(),
892                access: AccessMode::Read,
893            },
894        ];
895        let event = backend
896            .submit(&queue, &program, &bindings, Timeout::Infinite)
897            .unwrap();
898
899        let mut output = [0; 7];
900        backend.read_buffer(&target, 0, &mut output).unwrap();
901        assert_eq!(output, [0; 7]);
902        backend.complete(&event).unwrap();
903        backend.read_buffer(&target, 0, &mut output).unwrap();
904        assert_eq!(&output, b"copy me");
905    }
906
907    #[test]
908    fn pending_operations_complete_in_harness_selected_order() {
909        let backend = MockAccelerator::default();
910        let context = backend.create_context(ContextDesc::default()).unwrap();
911        let allocation = backend
912            .allocate_buffer(
913                &context,
914                BufferDesc::new(
915                    4,
916                    1,
917                    MemoryDomain::Shared,
918                    BufferUsage::TRANSFER_SOURCE
919                        | BufferUsage::PROGRAM_OUTPUT
920                        | BufferUsage::MUTABLE_STATE,
921                )
922                .unwrap(),
923            )
924            .unwrap();
925        let (buffer, _) = allocation.into_parts();
926        let fill = reference::ReferenceArtifact::fill(0, 0xa5);
927        let xor = reference::ReferenceArtifact::xor(0, 0xff);
928        let fill_program = load_reference(&backend, &context, &fill);
929        let xor_program = load_reference(&backend, &context, &xor);
930        let queue = backend
931            .create_queue(&context, QueueDesc::default())
932            .unwrap();
933        let range = BufferRange::new(0, 4).unwrap();
934        let fill_bindings = [BindingRef {
935            slot: 0,
936            buffer: &buffer,
937            range,
938            access: AccessMode::Write,
939        }];
940        let xor_bindings = [BindingRef {
941            slot: 0,
942            buffer: &buffer,
943            range,
944            access: AccessMode::ReadWrite,
945        }];
946        let fill_event = backend
947            .submit(&queue, &fill_program, &fill_bindings, Timeout::Infinite)
948            .unwrap();
949        let xor_event = backend
950            .submit(&queue, &xor_program, &xor_bindings, Timeout::Infinite)
951            .unwrap();
952
953        backend.complete(&xor_event).unwrap();
954        let mut output = [0; 4];
955        backend.read_buffer(&buffer, 0, &mut output).unwrap();
956        assert_eq!(output, [0xff; 4]);
957        assert_eq!(backend.poll_event(&fill_event), Ok(EventState::Pending));
958
959        backend.complete(&fill_event).unwrap();
960        backend.read_buffer(&buffer, 0, &mut output).unwrap();
961        assert_eq!(output, [0xa5; 4]);
962    }
963
964    #[test]
965    fn cancellation_and_device_loss_prevent_execution() {
966        let backend = MockAccelerator::default();
967        let context = backend.create_context(ContextDesc::default()).unwrap();
968        let allocation = backend
969            .allocate_buffer(
970                &context,
971                BufferDesc::new(
972                    4,
973                    1,
974                    MemoryDomain::Host,
975                    BufferUsage::TRANSFER_SOURCE | BufferUsage::PROGRAM_OUTPUT,
976                )
977                .unwrap(),
978            )
979            .unwrap();
980        let (buffer, _) = allocation.into_parts();
981        let artifact = reference::ReferenceArtifact::fill(0, 0x5a);
982        let program = load_reference(&backend, &context, &artifact);
983        let queue = backend
984            .create_queue(&context, QueueDesc::default())
985            .unwrap();
986        let bindings = [BindingRef {
987            slot: 0,
988            buffer: &buffer,
989            range: BufferRange::new(0, 4).unwrap(),
990            access: AccessMode::Write,
991        }];
992
993        let cancelled = backend
994            .submit(&queue, &program, &bindings, Timeout::Infinite)
995            .unwrap();
996        backend.cancel_event(&cancelled).unwrap();
997        assert_eq!(backend.complete(&cancelled), Err(BackendError::Busy));
998
999        let lost = backend
1000            .submit(&queue, &program, &bindings, Timeout::Infinite)
1001            .unwrap();
1002        backend.fail_device_lost(&lost).unwrap();
1003        assert_eq!(
1004            backend.poll_event(&lost),
1005            Ok(EventState::Failed(BackendError::DeviceLost))
1006        );
1007        assert_eq!(backend.complete(&lost), Err(BackendError::Busy));
1008
1009        let mut output = [0; 4];
1010        backend.read_buffer(&buffer, 0, &mut output).unwrap();
1011        assert_eq!(output, [0; 4]);
1012    }
1013
1014    #[test]
1015    fn artifact_and_binding_incompatibility_fail_before_admission() {
1016        let backend = MockAccelerator::default();
1017        let context = backend.create_context(ContextDesc::default()).unwrap();
1018        let artifact = reference::ReferenceArtifact::fill(2, 1);
1019
1020        assert!(matches!(
1021            backend.load_program(
1022                &context,
1023                ArtifactRef {
1024                    format: reference::ARTIFACT_FORMAT,
1025                    target: TargetIdentity([0; 12]),
1026                    payload: artifact.as_bytes(),
1027                    resident_bytes: reference::RESIDENT_BYTES,
1028                },
1029            ),
1030            Err(BackendError::Incompatible)
1031        ));
1032
1033        let mut malformed = *artifact.as_bytes();
1034        malformed[17] = 1;
1035        assert!(matches!(
1036            backend.load_program(
1037                &context,
1038                ArtifactRef {
1039                    format: reference::ARTIFACT_FORMAT,
1040                    target: reference::TARGET_IDENTITY,
1041                    payload: &malformed,
1042                    resident_bytes: reference::RESIDENT_BYTES,
1043                },
1044            ),
1045            Err(BackendError::InvalidArgument)
1046        ));
1047        let program = load_reference(&backend, &context, &artifact);
1048
1049        let allocation = backend
1050            .allocate_buffer(
1051                &context,
1052                BufferDesc::new(4, 1, MemoryDomain::Host, BufferUsage::MUTABLE_STATE).unwrap(),
1053            )
1054            .unwrap();
1055        let (buffer, _) = allocation.into_parts();
1056        let queue = backend
1057            .create_queue(&context, QueueDesc::default())
1058            .unwrap();
1059        let bindings = [BindingRef {
1060            slot: 2,
1061            buffer: &buffer,
1062            range: BufferRange::new(0, 4).unwrap(),
1063            access: AccessMode::ReadWrite,
1064        }];
1065        assert!(matches!(
1066            backend.submit(&queue, &program, &bindings, Timeout::Infinite),
1067            Err(SubmitFailure::Rejected(BackendError::Incompatible))
1068        ));
1069    }
1070
1071    #[test]
1072    fn usage_mismatch_is_rejected_before_admission() {
1073        let backend = MockAccelerator::default();
1074        let context = backend.create_context(ContextDesc::default()).unwrap();
1075        let allocation = backend
1076            .allocate_buffer(
1077                &context,
1078                BufferDesc::new(16, 1, MemoryDomain::Host, BufferUsage::PROGRAM_INPUT).unwrap(),
1079            )
1080            .unwrap();
1081        let (buffer, _) = allocation.into_parts();
1082        let artifact = reference::ReferenceArtifact::barrier(0);
1083        let program = load_reference(&backend, &context, &artifact);
1084        let queue = backend
1085            .create_queue(&context, QueueDesc::default())
1086            .unwrap();
1087        let bindings = [BindingRef {
1088            slot: 0,
1089            buffer: &buffer,
1090            range: BufferRange::new(0, 16).unwrap(),
1091            access: AccessMode::Write,
1092        }];
1093        assert!(matches!(
1094            backend.submit(&queue, &program, &bindings, Timeout::Infinite),
1095            Err(SubmitFailure::Rejected(BackendError::PermissionDenied))
1096        ));
1097        assert_eq!(backend.direct_binding_admissions(), 0);
1098    }
1099}