use std::ffi::c_void;
use std::ptr::NonNull;
use onnx_runtime_ir::{DeviceId, DeviceType, Graph, Node, NodeId, Shape, TensorLayout};
use crate::error::{EpError, Result};
use crate::epcontext::EpContext;
use crate::kernel::{Kernel, KernelMatch};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct EpId(pub u32);
#[derive(Clone, Debug, Default)]
pub struct EpConfig {
pub options: std::collections::HashMap<String, String>,
}
#[derive(Debug)]
pub struct DeviceBuffer {
device: DeviceId,
size: usize,
align: usize,
ptr: NonNull<c_void>,
owner: BufferOwner,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BufferOwner {
Owned,
Borrowed,
}
impl DeviceBuffer {
pub unsafe fn from_raw_parts(
ptr: *mut c_void,
device: DeviceId,
size: usize,
align: usize,
) -> Self {
debug_assert!(align.is_power_of_two(), "alignment must be a power of two");
Self {
device,
size,
align,
ptr: NonNull::new(ptr).expect("DeviceBuffer::from_raw_parts: null pointer"),
owner: BufferOwner::Owned,
}
}
pub unsafe fn from_borrowed_parts(
ptr: *mut c_void,
device: DeviceId,
size: usize,
align: usize,
) -> Self {
debug_assert!(align.is_power_of_two(), "alignment must be a power of two");
Self {
device,
size,
align,
ptr: NonNull::new(ptr).expect("DeviceBuffer::from_borrowed_parts: null pointer"),
owner: BufferOwner::Borrowed,
}
}
pub fn is_borrowed(&self) -> bool {
matches!(self.owner, BufferOwner::Borrowed)
}
pub fn device(&self) -> DeviceId {
self.device
}
pub fn len(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn alignment(&self) -> usize {
self.align
}
pub fn as_ptr(&self) -> *const c_void {
self.ptr.as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut c_void {
self.ptr.as_ptr()
}
pub fn into_raw(self) -> *mut c_void {
self.ptr.as_ptr()
}
}
unsafe impl Send for DeviceBuffer {}
unsafe impl Sync for DeviceBuffer {}
#[derive(Debug, Default)]
pub struct Fence {
pub id: u64,
}
#[derive(Debug, Default)]
pub struct OrtPluginExport {
pub register_symbol: String,
}
pub trait OptimizerPass: Send + Sync {
fn name(&self) -> &str;
}
pub trait ExecutionProvider: Send + Sync {
fn name(&self) -> &str;
fn device_type(&self) -> DeviceType;
fn device_id(&self) -> DeviceId;
fn initialize(&mut self, config: &EpConfig) -> Result<()>;
fn shutdown(&mut self) -> Result<()>;
fn supports_op(&self, op: &Node, shapes: &[Shape], layouts: &[TensorLayout]) -> KernelMatch;
fn get_kernel(&self, op: &Node, shapes: &[Vec<usize>], opset: u64)
-> Result<Box<dyn Kernel>>;
fn allocate(&self, size: usize, alignment: usize) -> Result<DeviceBuffer>;
fn deallocate(&self, buffer: DeviceBuffer) -> Result<()>;
fn copy(&self, src: &DeviceBuffer, dst: &mut DeviceBuffer, size: usize) -> Result<()>;
fn copy_async(&self, src: &DeviceBuffer, dst: &mut DeviceBuffer, size: usize) -> Result<Fence>;
fn sync(&self) -> Result<()>;
fn as_ort_plugin(&self) -> Option<OrtPluginExport> {
None
}
fn custom_passes(&self) -> Vec<Box<dyn OptimizerPass>> {
Vec::new()
}
fn claim_nodes(&self, graph: &Graph) -> Vec<NodeId> {
let _ = graph;
Vec::new()
}
fn context_source_keys(&self) -> Vec<String> {
Vec::new()
}
fn save_context(&self) -> Result<EpContext> {
Err(EpError::UnsupportedContext {
ep: self.name().to_string(),
})
}
fn load_context(&self, ctx: &EpContext) -> Result<()> {
let _ = ctx;
Err(EpError::UnsupportedContext {
ep: self.name().to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn _assert_send_sync<T: Send + Sync>() {}
fn host_alloc(size: usize, align: usize) -> DeviceBuffer {
let boxed = vec![0u8; size].into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut c_void;
unsafe { DeviceBuffer::from_raw_parts(ptr, DeviceId::cpu(), size, align) }
}
fn host_free(buf: DeviceBuffer) {
let size = buf.len();
let ptr = buf.into_raw() as *mut u8;
unsafe {
drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, size)));
}
}
#[test]
fn device_buffer_is_send_sync() {
_assert_send_sync::<DeviceBuffer>();
}
#[test]
fn buffer_metadata_and_single_free() {
let mut buf = host_alloc(128, 64);
assert_eq!(buf.len(), 128);
assert!(!buf.is_empty());
assert_eq!(buf.alignment(), 64);
assert_eq!(buf.device(), DeviceId::cpu());
assert!(!buf.as_ptr().is_null());
assert!(!buf.as_mut_ptr().is_null());
host_free(buf);
}
#[test]
fn buffer_moves_across_thread() {
let buf = host_alloc(64, 16);
let base = buf.as_ptr() as usize;
let handle = std::thread::spawn(move || {
assert_eq!(buf.len(), 64);
assert_eq!(buf.as_ptr() as usize, base);
buf });
let buf = handle.join().unwrap();
host_free(buf);
}
#[test]
fn owned_buffer_is_not_borrowed() {
let buf = host_alloc(32, 16);
assert!(
!buf.is_borrowed(),
"from_raw_parts must produce an owned buffer"
);
host_free(buf);
}
#[test]
fn borrowed_buffer_aliases_without_owning() {
let mut backing = vec![7u8; 64];
let ptr = backing.as_mut_ptr() as *mut c_void;
let buf = unsafe { DeviceBuffer::from_borrowed_parts(ptr, DeviceId::cpu(), 64, 1) };
assert!(buf.is_borrowed());
assert_eq!(buf.len(), 64);
assert_eq!(buf.as_ptr(), ptr as *const c_void);
let raw = buf.into_raw();
assert_eq!(raw, ptr);
assert!(backing.iter().all(|&b| b == 7));
backing[0] = 9;
assert_eq!(backing[0], 9);
}
}