Skip to main content

iree_embedded/
device.rs

1//! A synchronous, single-threaded CPU device.
2//!
3//! Two loaders are offered: the embedded-ELF loader (host milestone; the
4//! `.vmfb` carries position-independent ELF kernels that are mapped into RAM)
5//! and the static-library loader (MCU; the kernels are compiled into the
6//! firmware image and execute in place from flash, costing no RAM).
7
8use crate::{Arena, Result, check};
9use iree_embedded_sys as sys;
10
11/// The `*_library_query` entry point emitted by
12/// `iree-compile --iree-llvmcpu-static-library-output-path=` (declared in the
13/// generated header alongside the `.o`). Declared with opaque pointers so
14/// firmware can `extern "C"` it without naming generated binding types; the
15/// shape is ABI-identical to `iree_hal_executable_library_query_fn_t`
16/// (`uint32_t`, pointer) -> pointer.
17pub type LibraryQueryFn = unsafe extern "C" fn(
18    max_version: u32,
19    environment: *const core::ffi::c_void,
20) -> *const core::ffi::c_void;
21
22/// A synchronous, single-threaded local CPU device that executes a model's
23/// kernels. Created with either the static-library loader
24/// ([`local_sync_static`](Device::local_sync_static)) or the embedded-ELF
25/// loader ([`local_sync`](Device::local_sync)).
26pub struct Device {
27    raw: *mut sys::iree_hal_device_t,
28}
29
30impl Device {
31    /// Device whose executables are statically linked into this binary.
32    ///
33    /// `libraries` are the query functions of every model linked into the
34    /// firmware; the loader resolves a `.vmfb`'s library reference by name.
35    pub fn local_sync_static(arena: &Arena, libraries: &[LibraryQueryFn]) -> Result<Self> {
36        let alloc = arena.as_iree_allocator();
37        // SAFETY: every out-pointer is valid; the arena outlives the device.
38        unsafe {
39            let mut loader = core::ptr::null_mut();
40            check(sys::iree_hal_static_library_loader_create(
41                libraries.len() as _,
42                // A non-null `LibraryQueryFn` has the same layout as the
43                // `Option`-wrapped bindgen fn pointer (niche optimization).
44                libraries.as_ptr() as *const sys::iree_hal_executable_library_query_fn_t,
45                sys::iree_hal_executable_import_provider_null(),
46                alloc,
47                &mut loader,
48            ))?;
49            Self::from_loader(loader, alloc)
50        }
51    }
52
53    /// Device using the embedded-ELF loader, for a `.vmfb` whose kernels are
54    /// position-independent ELF mapped into RAM (the host-test path; the MCU
55    /// uses [`local_sync_static`](Device::local_sync_static)).
56    pub fn local_sync(arena: &Arena) -> Result<Self> {
57        let alloc = arena.as_iree_allocator();
58        // SAFETY: every out-pointer is valid; the arena outlives the device.
59        unsafe {
60            let mut loader = core::ptr::null_mut();
61            check(sys::iree_hal_embedded_elf_loader_create(
62                core::ptr::null_mut(), // plugin_manager
63                alloc,
64                &mut loader,
65            ))?;
66            Self::from_loader(loader, alloc)
67        }
68    }
69
70    /// Build the local-sync device around `loader`, consuming one reference to
71    /// it (released here whether or not creation succeeds).
72    ///
73    /// # Safety
74    /// `loader` must be a valid executable loader and `alloc` must outlive the
75    /// returned device.
76    unsafe fn from_loader(
77        mut loader: *mut sys::iree_hal_executable_loader_t,
78        alloc: sys::iree_allocator_t,
79    ) -> Result<Self> {
80        unsafe {
81            let id = sys::iree_make_cstring_view(c"local-sync".as_ptr());
82
83            let mut device_allocator = core::ptr::null_mut();
84            let status =
85                sys::iree_hal_allocator_create_heap(id, alloc, alloc, &mut device_allocator);
86
87            let mut params: sys::iree_hal_sync_device_params_t = core::mem::zeroed();
88            sys::iree_hal_sync_device_params_initialize(&mut params);
89            // The default 32 KiB transient block is host-sized; on an MCU it
90            // starves the arena. 4 KiB is the device's documented minimum and
91            // blocks chain on demand.
92            params.arena_block_size = 4096;
93
94            let mut raw = core::ptr::null_mut();
95            let status = if status.is_null() {
96                sys::iree_hal_sync_device_create(
97                    id,
98                    &params,
99                    1, // loader_count
100                    &mut loader,
101                    device_allocator,
102                    alloc,
103                    &mut raw,
104                )
105            } else {
106                status
107            };
108
109            sys::iree_hal_allocator_release(device_allocator);
110            sys::iree_hal_executable_loader_release(loader);
111            check(status)?;
112            Ok(Device { raw })
113        }
114    }
115}
116
117impl Device {
118    pub(crate) fn raw(&self) -> *mut sys::iree_hal_device_t {
119        self.raw
120    }
121}
122
123impl Drop for Device {
124    fn drop(&mut self) {
125        // SAFETY: raw was created by iree_hal_sync_device_create.
126        unsafe { sys::iree_hal_device_release(self.raw) };
127    }
128}