Skip to main content

virtio_accel_vaccel/
lib.rs

1//! vAccel integration boundary for portable `virtio-accel` contracts.
2//!
3//! This crate owns the adapter seam for host-side integrations that need to project an
4//! external execution model into the [`virtio_accel_core::Accelerator`] contract.
5//!
6//! The adapter is intentionally conservative:
7//! - It never adds host/VMM dependencies to portable crates.
8//! - It forwards lifecycle, allocation, submission, and release calls to the wrapped backend.
9//! - It exposes best-effort copy-path diagnostics (`direct_binding_admissions` and
10//!   `explicit_transfer_bytes`) to keep integration evidence visible in conformance harnesses.
11
12#![forbid(unsafe_code)]
13#![no_std]
14
15use core::sync::atomic::{AtomicU64, Ordering};
16use core::{convert::TryFrom, result::Result};
17
18use virtio_accel_core::{
19    Accelerator, AllocatedBuffer, ArtifactRef, BackendError, BindingRef, BufferDesc, ByteSink,
20    ByteSource, ContextDesc, DeviceInfo, EventState, QueueDesc, ReleaseFailure, SubmitFailure,
21    Timeout,
22};
23
24#[derive(Debug, Default)]
25struct SubmissionPathCounters {
26    direct_bindings: AtomicU64,
27    explicit_transfer_bytes: AtomicU64,
28}
29
30impl SubmissionPathCounters {
31    fn direct_binding_admissions(&self) -> u64 {
32        self.direct_bindings.load(Ordering::Relaxed)
33    }
34
35    fn explicit_transfer_bytes(&self) -> u64 {
36        self.explicit_transfer_bytes.load(Ordering::Relaxed)
37    }
38
39    fn reset(&self) {
40        self.direct_bindings.store(0, Ordering::Relaxed);
41        self.explicit_transfer_bytes.store(0, Ordering::Relaxed);
42    }
43
44    fn record_submission(&self, bindings: usize) {
45        self.direct_bindings.fetch_add(
46            u64::try_from(bindings).unwrap_or(u64::MAX),
47            Ordering::Relaxed,
48        );
49    }
50
51    fn record_explicit_transfer(&self, bytes: u64) {
52        self.explicit_transfer_bytes
53            .fetch_add(bytes, Ordering::Relaxed);
54    }
55}
56
57/// Adapter seam from a concrete backend to the portable accelerator contract.
58#[derive(Debug)]
59pub struct VAccelAdapter<A> {
60    inner: A,
61    counters: SubmissionPathCounters,
62}
63
64impl<A> VAccelAdapter<A> {
65    /// Create a new adapter around a concrete backend.
66    pub fn new(inner: A) -> Self {
67        Self {
68            inner,
69            counters: SubmissionPathCounters::default(),
70        }
71    }
72
73    /// Borrow the wrapped backend to reach provider-specific metrics.
74    pub fn backend(&self) -> &A {
75        &self.inner
76    }
77
78    /// Borrow the wrapped backend mutably.
79    pub fn backend_mut(&mut self) -> &mut A {
80        &mut self.inner
81    }
82
83    /// Consume the adapter and return the underlying backend.
84    pub fn into_inner(self) -> A {
85        self.inner
86    }
87
88    /// Number of observed accepted binding admissions.
89    ///
90    /// This counter is a first-order signal for backends that do not expose their native
91    /// direct-bindings counters yet.
92    pub fn direct_binding_admissions(&self) -> u64 {
93        self.counters.direct_binding_admissions()
94    }
95
96    /// Total bytes passed through explicit host transfers.
97    ///
98    /// This includes bytes requested by explicit `write_buffer` and `read_buffer` calls.
99    pub fn explicit_transfer_bytes(&self) -> u64 {
100        self.counters.explicit_transfer_bytes()
101    }
102
103    /// Reset adapter-level metrics collected during the current integration window.
104    pub fn reset_submission_path_metrics(&self) {
105        self.counters.reset();
106    }
107}
108
109impl<A: Accelerator> Accelerator for VAccelAdapter<A> {
110    type Context = A::Context;
111    type Buffer = A::Buffer;
112    type Program = A::Program;
113    type Queue = A::Queue;
114    type Event = A::Event;
115
116    fn device_info(&self) -> Result<DeviceInfo, BackendError> {
117        self.inner.device_info()
118    }
119
120    fn create_context(&self, desc: ContextDesc) -> Result<Self::Context, BackendError> {
121        self.inner.create_context(desc)
122    }
123
124    fn destroy_context(&self, context: Self::Context) -> Result<(), ReleaseFailure<Self::Context>> {
125        self.inner.destroy_context(context)
126    }
127
128    fn allocate_buffer(
129        &self,
130        context: &Self::Context,
131        desc: BufferDesc,
132    ) -> Result<AllocatedBuffer<Self::Buffer>, BackendError> {
133        self.inner.allocate_buffer(context, desc)
134    }
135
136    fn write_buffer(
137        &self,
138        buffer: &mut Self::Buffer,
139        offset: u64,
140        data: &dyn ByteSource,
141    ) -> Result<(), BackendError> {
142        let result = self.inner.write_buffer(buffer, offset, data);
143        if result.is_ok() {
144            self.counters.record_explicit_transfer(data.len());
145        }
146        result
147    }
148
149    fn read_buffer(
150        &self,
151        buffer: &Self::Buffer,
152        offset: u64,
153        data: &mut dyn ByteSink,
154    ) -> Result<(), BackendError> {
155        let result = self.inner.read_buffer(buffer, offset, data);
156        if result.is_ok() {
157            self.counters.record_explicit_transfer(data.len());
158        }
159        result
160    }
161
162    fn free_buffer(&self, buffer: Self::Buffer) -> Result<(), ReleaseFailure<Self::Buffer>> {
163        self.inner.free_buffer(buffer)
164    }
165
166    fn load_program(
167        &self,
168        context: &Self::Context,
169        artifact: ArtifactRef<'_>,
170    ) -> Result<Self::Program, BackendError> {
171        self.inner.load_program(context, artifact)
172    }
173
174    fn unload_program(&self, program: Self::Program) -> Result<(), ReleaseFailure<Self::Program>> {
175        self.inner.unload_program(program)
176    }
177
178    fn create_queue(
179        &self,
180        context: &Self::Context,
181        desc: QueueDesc,
182    ) -> Result<Self::Queue, BackendError> {
183        self.inner.create_queue(context, desc)
184    }
185
186    fn destroy_queue(&self, queue: Self::Queue) -> Result<(), ReleaseFailure<Self::Queue>> {
187        self.inner.destroy_queue(queue)
188    }
189
190    fn submit(
191        &self,
192        queue: &Self::Queue,
193        program: &Self::Program,
194        bindings: &[BindingRef<'_, Self::Buffer>],
195        timeout: Timeout,
196    ) -> Result<Self::Event, SubmitFailure<Self::Event>> {
197        let result = self.inner.submit(queue, program, bindings, timeout);
198        match &result {
199            Ok(_) => self.counters.record_submission(bindings.len()),
200            Err(SubmitFailure::Indeterminate { .. }) => {
201                self.counters.record_submission(bindings.len())
202            }
203            Err(SubmitFailure::Rejected(_)) => {}
204        }
205        result
206    }
207
208    fn poll_event(&self, event: &Self::Event) -> Result<EventState, BackendError> {
209        self.inner.poll_event(event)
210    }
211
212    fn cancel_event(&self, event: &Self::Event) -> Result<(), BackendError> {
213        self.inner.cancel_event(event)
214    }
215
216    fn destroy_event(&self, event: Self::Event) -> Result<(), ReleaseFailure<Self::Event>> {
217        self.inner.destroy_event(event)
218    }
219}