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}