From bd5c57d65fdf3d415c1e3e0232013130f48ea1f4 Mon Sep 17 00:00:00 2001 From: Alexander Droste Date: Wed, 3 Jun 2026 15:36:37 +0000 Subject: [PATCH] feat: add cuDF C FFI Signed-off-by: "Alexander Droste" Signed-off-by: Alexander Droste --- Cargo.lock | 13 + Cargo.toml | 1 + vortex-cuda/ffi/Cargo.toml | 33 +++ vortex-cuda/ffi/README.md | 16 ++ vortex-cuda/ffi/cinclude/vortex_cuda.h | 78 ++++++ vortex-cuda/ffi/src/lib.rs | 359 +++++++++++++++++++++++++ vortex-cuda/src/session.rs | 20 +- vortex-ffi/Cargo.toml | 2 +- vortex-ffi/src/array.rs | 3 + vortex-ffi/src/lib.rs | 38 +++ vortex-ffi/src/macros.rs | 4 + 11 files changed, 561 insertions(+), 6 deletions(-) create mode 100644 vortex-cuda/ffi/Cargo.toml create mode 100644 vortex-cuda/ffi/README.md create mode 100644 vortex-cuda/ffi/cinclude/vortex_cuda.h create mode 100644 vortex-cuda/ffi/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9189591e620..5160459c68f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9475,6 +9475,19 @@ dependencies = [ "vortex-nvcomp", ] +[[package]] +name = "vortex-cuda-ffi" +version = "0.1.0" +dependencies = [ + "arrow-schema", + "futures", + "vortex", + "vortex-array", + "vortex-cuda", + "vortex-cuda-macros", + "vortex-ffi", +] + [[package]] name = "vortex-cuda-macros" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index e3c3cbae67e..5c448999733 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "vortex-duckdb", "vortex-cuda", "vortex-cuda/cub", + "vortex-cuda/ffi", "vortex-cuda/gpu-scan-cli", "vortex-cuda/macros", "vortex-cuda/nvcomp", diff --git a/vortex-cuda/ffi/Cargo.toml b/vortex-cuda/ffi/Cargo.toml new file mode 100644 index 00000000000..a8ca51ad954 --- /dev/null +++ b/vortex-cuda/ffi/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "vortex-cuda-ffi" +description = "Native C FFI bindings for Vortex CUDA interop" +readme = "README.md" +publish = false +version = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +keywords = { workspace = true } +include = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +categories = { workspace = true } + +[dependencies] +arrow-schema = { workspace = true, features = ["ffi"] } +futures = { workspace = true, features = ["executor"] } +vortex = { workspace = true, features = ["object_store"] } +vortex-cuda = { path = ".." } +vortex-ffi = { path = "../../vortex-ffi" } + +[dev-dependencies] +vortex-array = { workspace = true, features = ["_test-harness"] } +vortex-cuda-macros = { workspace = true } + +[lib] +name = "vortex_cuda_ffi" +crate-type = ["rlib", "staticlib", "cdylib"] + +[lints] +workspace = true diff --git a/vortex-cuda/ffi/README.md b/vortex-cuda/ffi/README.md new file mode 100644 index 00000000000..6ccb5f738f3 --- /dev/null +++ b/vortex-cuda/ffi/README.md @@ -0,0 +1,16 @@ +# vortex-cuda-ffi + +CUDA-specific C FFI helpers for cuDF interop. + +This crate keeps CUDA out of the base `vortex-ffi` crate. Its public C API exports a borrowed `vx_array` as an `ArrowSchema + ArrowDeviceArray` pair. + +It does not create cuDF objects itself. The caller passes the exported Arrow Device structs to cuDF and releases them after cuDF is done importing. + +Use this crate as the CUDA-enabled FFI artifact. Include both headers: + +```c +#include "vortex.h" +#include "vortex_cuda.h" +``` + +and link the CUDA FFI library (`vortex_cuda_ffi`). Do not pass Vortex handles between independently linked Rust FFI libraries. diff --git a/vortex-cuda/ffi/cinclude/vortex_cuda.h b/vortex-cuda/ffi/cinclude/vortex_cuda.h new file mode 100644 index 00000000000..c04603140fa --- /dev/null +++ b/vortex-cuda/ffi/cinclude/vortex_cuda.h @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors +#pragma once + +#include + +#include "vortex.h" + +/* Link against the CUDA-enabled FFI library that provides both the base Vortex FFI and these CUDA + * entry points. Do not pass Vortex handles between independently linked Rust FFI libraries. */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef USE_OWN_ARROW_DEVICE +typedef int32_t ArrowDeviceType; +#define ARROW_DEVICE_CPU 1 +#define ARROW_DEVICE_CUDA 2 +#define ARROW_DEVICE_CUDA_HOST 3 +#define ARROW_DEVICE_OPENCL 4 +#define ARROW_DEVICE_VULKAN 7 +#define ARROW_DEVICE_METAL 8 +#define ARROW_DEVICE_VPI 9 +#define ARROW_DEVICE_ROCM 10 +#define ARROW_DEVICE_ROCM_HOST 11 +#define ARROW_DEVICE_EXT_DEV 12 +#define ARROW_DEVICE_CUDA_MANAGED 13 +#define ARROW_DEVICE_ONEAPI 14 +#define ARROW_DEVICE_WEBGPU 15 +#define ARROW_DEVICE_HEXAGON 16 + +struct ArrowDeviceArray { + struct ArrowArray array; + int64_t device_id; + ArrowDeviceType device_type; + void *sync_event; + int64_t reserved[3]; +}; +#endif + +typedef struct vx_cuda_error vx_cuda_error; + +/** + * Return the message for a CUDA FFI error. + * + * The returned pointer is valid until `error` is freed with `vx_cuda_error_free`. + */ +const char *vx_cuda_error_get_message(const vx_cuda_error *error); + +/** Free a CUDA FFI error. */ +void vx_cuda_error_free(vx_cuda_error *error); + +/** + * Export a borrowed Vortex array for cuDF's Arrow Device import path. + * + * On success, returns 0 and writes independently releasable `out_schema` and `out_array` values. + * This function does not create a cuDF object; the caller passes these outputs to cuDF and releases + * both with their embedded Arrow callbacks after cuDF is done importing. + * + * Guarantees: + * - `out_array->device_type == ARROW_DEVICE_CUDA`. + * - `out_array->device_id` identifies the CUDA device for every exported buffer. + * - Struct arrays export as table-shaped schemas; non-struct arrays export as column fields. + * - Schema/layout remapping matches Vortex's CUDA exporter, including decimals and lists. + * + * On error, returns 1 and writes `error_out` when it is not NULL. + */ +int vx_cuda_array_export_arrow_device(const vx_session *session, + const vx_array *array, + FFI_ArrowSchema *out_schema, + struct ArrowDeviceArray *out_array, + vx_cuda_error **error_out); + + +#ifdef __cplusplus +} +#endif diff --git a/vortex-cuda/ffi/src/lib.rs b/vortex-cuda/ffi/src/lib.rs new file mode 100644 index 00000000000..9e89817dae0 --- /dev/null +++ b/vortex-cuda/ffi/src/lib.rs @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +#![deny(missing_docs)] +#![expect(non_camel_case_types)] + +//! Native CUDA FFI helpers for cuDF interop. +//! +//! This crate keeps CUDA out of `vortex-ffi` and exports borrowed `vx_array` handles as the +//! `ArrowSchema + ArrowDeviceArray` pair that callers pass to cuDF's Arrow Device import APIs. + +use std::ffi::CString; +use std::ffi::c_char; +use std::os::raw::c_int; +use std::ptr; + +use arrow_schema::ffi::FFI_ArrowSchema; +use vortex::error::VortexResult; +use vortex::error::vortex_ensure; +use vortex::session::SessionExt; +use vortex::session::VortexSession; +use vortex_cuda::CudaSession; +/// Arrow C Device data interface array. +pub use vortex_cuda::arrow::ArrowDeviceArray; +use vortex_cuda::arrow::DeviceArrayExt; +use vortex_ffi::vx_array; +use vortex_ffi::vx_array_borrow; +use vortex_ffi::vx_session; +use vortex_ffi::vx_session_borrow; + +const VX_CUDA_OK: c_int = 0; +const VX_CUDA_ERR: c_int = 1; + +/// Error returned by fallible CUDA FFI functions. +pub struct vx_cuda_error { + message: CString, +} + +impl vx_cuda_error { + fn new(message: impl AsRef) -> *mut Self { + let mut bytes = message + .as_ref() + .as_bytes() + .iter() + .copied() + .filter(|byte| *byte != 0) + .collect::>(); + bytes.push(0); + // SAFETY: We just removed all interior NUL bytes and appended exactly one trailing NUL. + let message = unsafe { CString::from_vec_with_nul_unchecked(bytes) }; + Box::into_raw(Box::new(Self { message })) + } +} + +fn write_error(error_out: *mut *mut vx_cuda_error, message: &str) { + if !error_out.is_null() { + unsafe { error_out.write(vx_cuda_error::new(message)) }; + } +} + +fn clear_error(error_out: *mut *mut vx_cuda_error) { + if !error_out.is_null() { + unsafe { error_out.write(ptr::null_mut()) }; + } +} + +fn try_or( + error_out: *mut *mut vx_cuda_error, + function: impl FnOnce() -> VortexResult, +) -> c_int { + match function() { + Ok(value) => { + clear_error(error_out); + value + } + Err(err) => { + write_error(error_out, &err.to_string()); + VX_CUDA_ERR + } + } +} + +fn session_with_cuda(session: &VortexSession) -> VortexResult { + if session.get_opt::().is_some() { + return Ok(session.clone()); + } + + Ok(session.clone().with_some(CudaSession::try_default()?)) +} + +/// Return the message for a CUDA FFI error. +/// +/// The returned pointer is valid until `error` is freed with [`vx_cuda_error_free`]. +/// +/// # Safety +/// +/// If `error` is non-null, it must point to a valid [`vx_cuda_error`] allocated by this library. +#[unsafe(no_mangle)] +pub unsafe extern "C-unwind" fn vx_cuda_error_get_message( + error: *const vx_cuda_error, +) -> *const c_char { + if error.is_null() { + return ptr::null(); + } + unsafe { &*error }.message.as_ptr() +} + +/// Free a CUDA FFI error. +/// +/// # Safety +/// +/// `error` must be null or a pointer returned by this library. It must not be freed more than once. +#[unsafe(no_mangle)] +pub unsafe extern "C-unwind" fn vx_cuda_error_free(error: *mut vx_cuda_error) { + if !error.is_null() { + unsafe { drop(Box::from_raw(error)) }; + } +} + +/// Export a borrowed Vortex array for cuDF's Arrow Device import path. +/// +/// On success, returns `0` and writes independently releasable `out_schema` and `out_array` +/// values. This function does not create a cuDF object; the caller passes these outputs to cuDF +/// and releases both with their embedded Arrow callbacks after cuDF is done importing. +/// +/// Guarantees: +/// +/// - `out_array->device_type == ARROW_DEVICE_CUDA`. +/// - `out_array->device_id` identifies the CUDA device for every exported buffer. +/// - Struct arrays export as table-shaped schemas; non-struct arrays export as column fields. +/// - Schema/layout remapping matches Vortex's CUDA exporter, including decimals and lists. +/// +/// On error, returns `1` and writes `error_out` when it is not null. +/// +/// # Safety +/// +/// `session` and `array` must be valid borrowed handles created by `vortex-ffi` or compatible +/// helpers in this crate. `out_schema` and `out_array` must be valid writable pointers. If +/// `error_out` is non-null, it must be valid for writing one error pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C-unwind" fn vx_cuda_array_export_arrow_device( + session: *const vx_session, + array: *const vx_array, + out_schema: *mut FFI_ArrowSchema, + out_array: *mut ArrowDeviceArray, + error_out: *mut *mut vx_cuda_error, +) -> c_int { + try_or(error_out, || { + vortex_ensure!(!out_schema.is_null(), "null ArrowSchema output"); + vortex_ensure!(!out_array.is_null(), "null ArrowDeviceArray output"); + + let session = session_with_cuda(unsafe { vx_session_borrow(session) }?)?; + let array = unsafe { vx_array_borrow(array) }?.clone(); + let mut ctx = CudaSession::create_execution_ctx(&session)?; + let exported = + futures::executor::block_on(array.export_device_array_with_schema(&mut ctx))?; + + unsafe { + ptr::write(out_schema, exported.schema); + ptr::write(out_array, exported.array); + } + Ok(VX_CUDA_OK) + }) +} + +#[cfg(test)] +mod tests { + use std::ptr; + use std::sync::Arc; + + use arrow_schema::Field; + use arrow_schema::Schema; + use vortex::VortexSessionDefault; + use vortex::array::ArrayRef; + use vortex::array::IntoArray; + use vortex::array::arrays::PrimitiveArray; + use vortex::array::arrays::StructArray; + use vortex::array::validity::Validity; + use vortex::error::VortexResult; + use vortex_cuda::arrow::ARROW_DEVICE_CUDA; + use vortex_cuda_macros::cuda_available; + use vortex_cuda_macros::cuda_not_available; + + use super::*; + + fn test_session(session: VortexSession) -> *mut vx_session { + Box::into_raw(Box::new(session)).cast::() + } + + unsafe fn free_test_session(session: *mut vx_session) { + unsafe { drop(Box::from_raw(session.cast::())) }; + } + + fn test_array(array: impl IntoArray) -> *const vx_array { + Arc::into_raw(Arc::new(array.into_array())).cast::() + } + + unsafe fn free_test_array(array: *const vx_array) { + unsafe { Arc::decrement_strong_count(array.cast::()) }; + } + + unsafe fn release_schema(schema: &mut FFI_ArrowSchema) { + unsafe { + if let Some(release) = schema.release { + release(schema); + } + } + } + + unsafe fn release_device_array(array: &mut ArrowDeviceArray) { + unsafe { + if let Some(release) = array.array.release { + release(&raw mut array.array); + } + } + } + + fn empty_device_array() -> ArrowDeviceArray { + ArrowDeviceArray { + array: vortex_cuda::arrow::ArrowArray::empty(), + device_id: 0, + device_type: 0, + sync_event: ptr::null_mut(), + reserved: [0; 3], + } + } + + #[cuda_available] + #[test] + fn test_export_primitive_arrow_device() { + let mut error = ptr::null_mut(); + let session = test_session(VortexSession::default()); + let array = test_array(PrimitiveArray::from_iter(0u32..5)); + let mut schema = FFI_ArrowSchema::empty(); + let mut device_array = empty_device_array(); + + let status = unsafe { + vx_cuda_array_export_arrow_device( + session, + array, + &raw mut schema, + &raw mut device_array, + &raw mut error, + ) + }; + assert_eq!(status, VX_CUDA_OK); + assert!(error.is_null()); + + let field = Field::try_from(&schema).expect("schema should be a field"); + assert_eq!(field.name(), ""); + assert_eq!(device_array.array.length, 5); + assert_eq!(device_array.array.n_buffers, 2); + assert_eq!(device_array.device_type, ARROW_DEVICE_CUDA); + assert_eq!(device_array.reserved, [0; 3]); + assert!(device_array.array.release.is_some()); + + unsafe { + release_device_array(&mut device_array); + release_schema(&mut schema); + free_test_array(array); + free_test_session(session); + } + } + + #[cuda_available] + #[test] + fn test_export_struct_arrow_device_table() -> VortexResult<()> { + let mut error = ptr::null_mut(); + let session = test_session(VortexSession::default()); + let array = test_array(StructArray::try_new( + ["ids", "values"].into(), + vec![ + PrimitiveArray::from_iter(0u32..3).into_array(), + PrimitiveArray::from_iter([10i64, 20, 30]).into_array(), + ], + 3, + Validity::NonNullable, + )?); + + let mut schema = FFI_ArrowSchema::empty(); + let mut device_array = empty_device_array(); + + let status = unsafe { + vx_cuda_array_export_arrow_device( + session, + array, + &raw mut schema, + &raw mut device_array, + &raw mut error, + ) + }; + assert_eq!(status, VX_CUDA_OK); + assert!(error.is_null()); + + let arrow_schema = Schema::try_from(&schema)?; + assert_eq!(arrow_schema.fields().len(), 2); + assert_eq!(arrow_schema.field(0).name(), "ids"); + assert_eq!(arrow_schema.field(1).name(), "values"); + + assert_eq!(device_array.device_type, ARROW_DEVICE_CUDA); + assert_eq!(device_array.reserved, [0; 3]); + assert_eq!(device_array.array.length, 3); + assert_eq!(device_array.array.n_buffers, 1); + assert_eq!(device_array.array.n_children, 2); + assert!(device_array.array.release.is_some()); + + let children = unsafe { std::slice::from_raw_parts(device_array.array.children, 2) }; + for child in children { + let child = unsafe { &**child }; + assert_eq!(child.length, 3); + assert_eq!(child.n_buffers, 2); + assert!(child.release.is_some()); + } + + unsafe { + release_device_array(&mut device_array); + assert!(device_array.array.release.is_none()); + release_schema(&mut schema); + free_test_array(array); + free_test_session(session); + } + Ok(()) + } + + #[cuda_not_available] + #[test] + fn test_export_reports_cuda_initialization_error() { + let session = test_session(VortexSession::default()); + let array = test_array(PrimitiveArray::from_iter(0u32..5)); + let mut schema = FFI_ArrowSchema::empty(); + let mut device_array = empty_device_array(); + let mut error = ptr::null_mut(); + + let status = unsafe { + vx_cuda_array_export_arrow_device( + session, + array, + &raw mut schema, + &raw mut device_array, + &raw mut error, + ) + }; + assert_eq!(status, VX_CUDA_ERR); + assert!(!error.is_null()); + let message = unsafe { std::ffi::CStr::from_ptr(vx_cuda_error_get_message(error)) } + .to_string_lossy() + .into_owned(); + assert!( + message.contains("failed to initialize CUDA device 0"), + "unexpected error: {message}" + ); + + unsafe { + vx_cuda_error_free(error); + free_test_array(array); + free_test_session(session); + } + } +} diff --git a/vortex-cuda/src/session.rs b/vortex-cuda/src/session.rs index 4ef1736021c..8d7ab51b406 100644 --- a/vortex-cuda/src/session.rs +++ b/vortex-cuda/src/session.rs @@ -9,6 +9,7 @@ use cudarc::driver::CudaContext; use vortex::array::ArrayId; use vortex::array::VortexSessionExecute; use vortex::error::VortexResult; +use vortex::error::vortex_err; use vortex::session::Ref; use vortex::session::SessionExt; use vortex::session::SessionVar; @@ -63,6 +64,18 @@ impl CudaSession { } } + /// Creates a default CUDA session using device 0, with all GPU array kernels preloaded. + /// + /// Unlike [`Default::default`], this returns an error instead of panicking when CUDA cannot be + /// initialized. + pub fn try_default() -> VortexResult { + let context = CudaContext::new(0) + .map_err(|err| vortex_err!("failed to initialize CUDA device 0: {err}"))?; + let this = Self::new(context); + initialize_cuda(&this); + Ok(this) + } + /// Creates a new CUDA execution context. pub fn create_execution_ctx( vortex_session: &vortex::session::VortexSession, @@ -140,12 +153,9 @@ impl Default for CudaSession { /// # Panics /// /// Panics if CUDA device 0 cannot be initialized. + #[expect(clippy::expect_used)] fn default() -> Self { - #[expect(clippy::expect_used)] - let context = CudaContext::new(0).expect("Failed to initialize CUDA device 0"); - let this = Self::new(context); - initialize_cuda(&this); - this + Self::try_default().expect("Failed to initialize CUDA device 0") } } diff --git a/vortex-ffi/Cargo.toml b/vortex-ffi/Cargo.toml index 83a0737e2bd..6407f97fe46 100644 --- a/vortex-ffi/Cargo.toml +++ b/vortex-ffi/Cargo.toml @@ -47,7 +47,7 @@ mimalloc = ["dep:mimalloc"] [lib] name = "vortex_ffi" -crate-type = ["staticlib", "cdylib"] +crate-type = ["rlib", "staticlib", "cdylib"] [lints] workspace = true diff --git a/vortex-ffi/src/array.rs b/vortex-ffi/src/array.rs index f70af840385..c13b3768a7a 100644 --- a/vortex-ffi/src/array.rs +++ b/vortex-ffi/src/array.rs @@ -111,6 +111,7 @@ pub unsafe extern "C-unwind" fn vx_array_is_primitive( } } +/// Validity representation for arrays constructed through the C FFI. #[repr(C)] pub enum vx_validity_type { /// Items can't be null @@ -124,8 +125,10 @@ pub enum vx_validity_type { VX_VALIDITY_ARRAY = 3, } +/// Array validity descriptor used by C FFI constructors. #[repr(C)] pub struct vx_validity { + /// The kind of validity represented by this descriptor. pub r#type: vx_validity_type, /// If type is not VX_VALIDITY_ARRAY, this is NULL. /// If type is VX_VALIDITY_ARRAY, this is set to an owned boolean validity diff --git a/vortex-ffi/src/lib.rs b/vortex-ffi/src/lib.rs index adc72ad1cf2..a5b890917c8 100644 --- a/vortex-ffi/src/lib.rs +++ b/vortex-ffi/src/lib.rs @@ -2,6 +2,11 @@ // SPDX-FileCopyrightText: Copyright the Vortex contributors #![deny(missing_docs)] +#![allow( + clippy::missing_safety_doc, + clippy::not_unsafe_ptr_arg_deref, + reason = "The base C FFI exposes many unsafe extern functions; their contracts are documented in the generated C header and surrounding API docs." +)] //! Native interface to Vortex arrays, types, files and streams. @@ -29,12 +34,17 @@ use std::ffi::c_char; use std::sync::Arc; use std::sync::LazyLock; +pub use array::vx_array; pub use log::vx_log_level; +pub use session::vx_session; +use vortex::array::ArrayRef; use vortex::dtype::FieldName; use vortex::error::VortexExpect; use vortex::error::VortexResult; +use vortex::error::vortex_ensure; use vortex::error::vortex_err; use vortex::io::runtime::current::CurrentThreadRuntime; +use vortex::session::VortexSession; #[cfg(all(feature = "mimalloc", not(miri)))] #[global_allocator] @@ -44,6 +54,34 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // TODO(ngates): also create a CurrentThreadPool to manage background worker threads. static RUNTIME: LazyLock = LazyLock::new(CurrentThreadRuntime::new); +/// Borrow a Vortex array handle created by this FFI crate. +/// +/// This is intended for sibling FFI crates that extend the base C API without duplicating handle +/// layouts. +/// +/// # Safety +/// +/// `ptr` must be a valid borrowed `vx_array` handle created by this crate and must remain alive for +/// the returned borrow. +pub unsafe fn vx_array_borrow(ptr: *const vx_array) -> VortexResult<&'static ArrayRef> { + vortex_ensure!(!ptr.is_null(), "null vx_array"); + Ok(vx_array::as_ref(ptr)) +} + +/// Borrow a Vortex session handle created by this FFI crate. +/// +/// This is intended for sibling FFI crates that extend the base C API without duplicating handle +/// layouts. +/// +/// # Safety +/// +/// `ptr` must be a valid borrowed `vx_session` handle created by this crate and must remain alive +/// for the returned borrow. +pub unsafe fn vx_session_borrow(ptr: *const vx_session) -> VortexResult<&'static VortexSession> { + vortex_ensure!(!ptr.is_null(), "null vx_session"); + Ok(vx_session::as_ref(ptr)) +} + pub(crate) unsafe fn to_string(ptr: *const c_char) -> String { let c_str = unsafe { CStr::from_ptr(ptr) }; c_str.to_string_lossy().into_owned() diff --git a/vortex-ffi/src/macros.rs b/vortex-ffi/src/macros.rs index 4b191f1c7f6..d6ca49f550d 100644 --- a/vortex-ffi/src/macros.rs +++ b/vortex-ffi/src/macros.rs @@ -49,6 +49,7 @@ macro_rules! arc_dyn_wrapper { ($(#[$meta:meta])* $T:ty, $ffi_ident:ident) => { paste::paste! { $(#[$meta])* + #[repr(transparent)] #[allow(non_camel_case_types)] pub struct $ffi_ident(std::sync::Arc<$T>); @@ -112,6 +113,7 @@ macro_rules! arc_wrapper { ($(#[$meta:meta])* $T:ty, $ffi_ident:ident) => { paste::paste! { $(#[$meta])* + #[repr(transparent)] #[allow(non_camel_case_types)] pub struct $ffi_ident($T); @@ -176,6 +178,7 @@ macro_rules! box_dyn_wrapper { ($(#[$meta:meta])* $T:ty, $ffi_ident:ident) => { paste::paste! { $(#[$meta])* + #[repr(transparent)] #[expect(non_camel_case_types)] pub struct $ffi_ident(Box<$T>); @@ -239,6 +242,7 @@ macro_rules! box_wrapper { ($(#[$meta:meta])* $T:ty, $ffi_ident:ident) => { paste::paste! { $(#[$meta])* + #[repr(transparent)] #[expect(non_camel_case_types)] pub struct $ffi_ident($T);