hurray_ffi/descriptor.rs
1//! Opaque tensor descriptor handle for the Hurray C ABI.
2//!
3//! [`HurrayDescriptor`] is an opaque heap-allocated handle wrapping a decoded
4//! [`hurray_core::TensorDescriptor`]. Callers create handles by decoding raw
5//! bytes via [`hurray_descriptor_decode`] and MUST release them exactly once
6//! via [`hurray_descriptor_destroy`].
7
8use std::ptr;
9
10use hurray_core::TensorDescriptor;
11
12use crate::{
13 panic::{catch, null_check},
14 status::{status_from_core_error, HurrayStatus, HURRAY_ERR_BUFFER_TOO_SMALL, HURRAY_OK},
15};
16
17// ── HurrayDescriptor ──────────────────────────────────────────────────────────
18
19/// Opaque handle to a decoded tensor descriptor.
20///
21/// This struct is **not** `#[repr(C)]`; its layout is an implementation
22/// detail. Callers MUST treat it as a black-box opaque pointer created by
23/// [`hurray_descriptor_decode`] and destroyed by [`hurray_descriptor_destroy`].
24pub struct HurrayDescriptor(Box<TensorDescriptor>);
25
26impl HurrayDescriptor {
27 /// Borrows the decoded descriptor behind this handle.
28 pub(crate) fn inner(&self) -> &TensorDescriptor {
29 &self.0
30 }
31
32 /// Wraps a descriptor this crate decoded itself, rather than one decoded from bytes
33 /// a C caller supplied.
34 pub(crate) fn from_descriptor(descriptor: TensorDescriptor) -> Box<Self> {
35 Box::new(Self(Box::new(descriptor)))
36 }
37}
38
39// ── Decode / destroy ──────────────────────────────────────────────────────────
40
41/// Decodes a binary tensor descriptor from a byte buffer.
42///
43/// On success, `*out_handle` is set to a newly allocated [`HurrayDescriptor`]
44/// that MUST be released via [`hurray_descriptor_destroy`].
45///
46/// # Arguments
47///
48/// - `bytes` — Non-null pointer to the first byte of the encoded descriptor.
49/// - `len` — Number of bytes readable at `bytes`.
50/// - `out_handle` — Non-null pointer to a `*mut HurrayDescriptor`; set on
51/// success.
52///
53/// # Safety
54///
55/// - `bytes` MUST be valid and readable for `len` bytes for the duration of
56/// this call.
57/// - `out_handle` MUST be a valid, non-null, writable pointer.
58#[no_mangle]
59pub unsafe extern "C" fn hurray_descriptor_decode(
60 bytes: *const u8,
61 len: usize,
62 out_handle: *mut *mut HurrayDescriptor,
63) -> HurrayStatus {
64 catch(|| {
65 null_check!(bytes, out_handle);
66
67 // SAFETY: caller guarantees bytes is valid for len bytes with lifetime
68 // at least as long as this call.
69 let slice = std::slice::from_raw_parts(bytes, len);
70
71 let desc = match TensorDescriptor::decode(slice) {
72 Ok(d) => d,
73 Err(e) => return status_from_core_error(&e),
74 };
75
76 // SAFETY: Box::into_raw transfers ownership to the caller; they must
77 // call hurray_descriptor_destroy exactly once.
78 *out_handle = Box::into_raw(Box::new(HurrayDescriptor(Box::new(desc))));
79 HURRAY_OK
80 })
81}
82
83/// Destroys a [`HurrayDescriptor`] handle and frees its memory.
84///
85/// After this call, `handle` is no longer valid and MUST NOT be dereferenced.
86///
87/// # Safety
88///
89/// - `handle` MUST have been created by [`hurray_descriptor_decode`].
90/// - `handle` MUST NOT have been previously destroyed.
91#[no_mangle]
92pub unsafe extern "C" fn hurray_descriptor_destroy(handle: *mut HurrayDescriptor) -> HurrayStatus {
93 catch(|| {
94 null_check!(handle);
95 // SAFETY: created by hurray_descriptor_decode via Box::into_raw; caller
96 // guarantees this is the first and only destroy call.
97 drop(Box::from_raw(handle));
98 HURRAY_OK
99 })
100}
101
102// ── Scalar accessors ──────────────────────────────────────────────────────────
103
104/// Reads the rank (number of dimensions) of the tensor descriptor.
105///
106/// # Safety
107///
108/// `handle` and `out_rank` MUST be valid, non-null pointers.
109#[no_mangle]
110pub unsafe extern "C" fn hurray_descriptor_rank(
111 handle: *const HurrayDescriptor,
112 out_rank: *mut u32,
113) -> HurrayStatus {
114 catch(|| {
115 null_check!(handle, out_rank);
116 // SAFETY: handle is non-null and points to a live HurrayDescriptor.
117 *out_rank = (*handle).0.shape.rank() as u32;
118 HURRAY_OK
119 })
120}
121
122/// Reads the element type tag byte of the tensor descriptor.
123///
124/// # Safety
125///
126/// `handle` and `out_tag` MUST be valid, non-null pointers.
127#[no_mangle]
128pub unsafe extern "C" fn hurray_descriptor_element_type_tag(
129 handle: *const HurrayDescriptor,
130 out_tag: *mut u8,
131) -> HurrayStatus {
132 catch(|| {
133 null_check!(handle, out_tag);
134 // SAFETY: handle is non-null and points to a live HurrayDescriptor.
135 *out_tag = (*handle).0.element_type.tag();
136 HURRAY_OK
137 })
138}
139
140/// Reads the layout tag byte of the tensor descriptor.
141///
142/// # Safety
143///
144/// `handle` and `out_tag` MUST be valid, non-null pointers.
145#[no_mangle]
146pub unsafe extern "C" fn hurray_descriptor_layout_tag(
147 handle: *const HurrayDescriptor,
148 out_tag: *mut u8,
149) -> HurrayStatus {
150 catch(|| {
151 null_check!(handle, out_tag);
152 // SAFETY: handle is non-null and points to a live HurrayDescriptor.
153 *out_tag = (*handle).0.layout.tag();
154 HURRAY_OK
155 })
156}
157
158/// Reads the byte offset from the start of buffer 0 to logical element `[0,…,0]`.
159///
160/// # Safety
161///
162/// `handle` and `out_offset` MUST be valid, non-null pointers.
163#[no_mangle]
164pub unsafe extern "C" fn hurray_descriptor_byte_offset(
165 handle: *const HurrayDescriptor,
166 out_offset: *mut u64,
167) -> HurrayStatus {
168 catch(|| {
169 null_check!(handle, out_offset);
170 // SAFETY: handle is non-null and points to a live HurrayDescriptor.
171 *out_offset = (*handle).0.byte_offset;
172 HURRAY_OK
173 })
174}
175
176/// Reads the number of buffer handles in the tensor descriptor's buffer table.
177///
178/// # Safety
179///
180/// `handle` and `out_count` MUST be valid, non-null pointers.
181#[no_mangle]
182pub unsafe extern "C" fn hurray_descriptor_buffer_count(
183 handle: *const HurrayDescriptor,
184 out_count: *mut u32,
185) -> HurrayStatus {
186 catch(|| {
187 null_check!(handle, out_count);
188 // SAFETY: handle is non-null and points to a live HurrayDescriptor.
189 *out_count = (*handle).0.buffers.len() as u32;
190 HURRAY_OK
191 })
192}
193
194// ── Shape accessor ────────────────────────────────────────────────────────────
195
196/// Reads the shape (dimension sizes) of a tensor descriptor.
197///
198/// This function uses a **capacity/length in-out pattern**:
199///
200/// 1. Set `*out_rank` to the capacity of the `out_dims` array (number of
201/// `uint64_t` elements it can hold), or to `0` if `out_dims` is null.
202/// 2. On return, `*out_rank` always contains the true rank of the tensor.
203/// 3. If `out_dims` is null OR the capacity was less than the true rank,
204/// the function returns [`HURRAY_ERR_BUFFER_TOO_SMALL`] and `*out_rank`
205/// contains the true rank so the caller can allocate and retry.
206/// 4. Otherwise the function writes the dimension sizes into `out_dims[0..rank]`
207/// and returns [`HURRAY_OK`].
208///
209/// # Safety
210///
211/// - `handle` and `out_rank` MUST be valid, non-null pointers.
212/// - If `out_dims` is non-null it MUST be writable for `capacity` × 8 bytes,
213/// where `capacity` is the value at `*out_rank` on entry.
214#[no_mangle]
215pub unsafe extern "C" fn hurray_descriptor_shape(
216 handle: *const HurrayDescriptor,
217 out_dims: *mut u64,
218 out_rank: *mut usize,
219) -> HurrayStatus {
220 catch(|| {
221 // out_dims may be null (capacity-query mode); out_rank is always required.
222 null_check!(handle, out_rank);
223
224 // SAFETY: handle and out_rank are non-null and point to live memory.
225 let shape = &(*handle).0.shape;
226 let true_rank = shape.rank();
227
228 // Read the caller-supplied capacity before overwriting *out_rank.
229 let capacity = *out_rank;
230
231 // Write the true rank unconditionally so the caller can use it to
232 // allocate on a BUFFER_TOO_SMALL retry.
233 *out_rank = true_rank;
234
235 if out_dims.is_null() || capacity < true_rank {
236 return HURRAY_ERR_BUFFER_TOO_SMALL;
237 }
238
239 // SAFETY: out_dims is non-null and writable for at least true_rank
240 // elements; shape.dims() has exactly true_rank elements.
241 ptr::copy_nonoverlapping(shape.dims().as_ptr(), out_dims, true_rank);
242 HURRAY_OK
243 })
244}