1#![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 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 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 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}