Skip to main content

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}