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