@@ -35,7 +35,6 @@ use vortex::buffer::ByteBuffer;
3535use vortex:: dtype:: DType ;
3636use vortex:: error:: VortexError ;
3737use vortex:: error:: VortexResult ;
38- use vortex:: error:: vortex_bail;
3938use vortex:: error:: vortex_ensure;
4039use vortex:: error:: vortex_err;
4140use vortex:: flatbuffers:: FlatBuffer ;
@@ -49,12 +48,115 @@ use vortex_cuda::arrow::ArrowDeviceArrayWithSchema;
4948use vortex_cuda:: arrow:: DeviceArrayExt ;
5049use vortex_cuda:: arrow:: release_device_array;
5150use vortex_cuda:: arrow:: release_schema;
51+ use vortex_python_abi:: BUFFER_EXPORT_CAPSULE_NAME ;
52+ use vortex_python_abi:: VORTEX_BUFFER_EXPORT_VERSION ;
53+ use vortex_python_abi:: VORTEX_BUFFER_HOST ;
54+ use vortex_python_abi:: VortexBufferExport ;
5255
5356const ARROW_SCHEMA_CAPSULE_NAME : & CStr = c_str ! ( "arrow_schema" ) ;
5457const USED_ARROW_SCHEMA_CAPSULE_NAME : & CStr = c_str ! ( "used_arrow_schema" ) ;
5558const ARROW_DEVICE_ARRAY_CAPSULE_NAME : & CStr = c_str ! ( "arrow_device_array" ) ;
5659const USED_ARROW_DEVICE_ARRAY_CAPSULE_NAME : & CStr = c_str ! ( "used_arrow_device_array" ) ;
5760
61+ struct BufferExportGuard {
62+ export : NonNull < VortexBufferExport > ,
63+ }
64+
65+ impl BufferExportGuard {
66+ fn export ( & self ) -> & VortexBufferExport {
67+ unsafe { self . export . as_ref ( ) }
68+ }
69+ }
70+
71+ impl AsRef < [ u8 ] > for BufferExportGuard {
72+ fn as_ref ( & self ) -> & [ u8 ] {
73+ let export = self . export ( ) ;
74+ if export. len == 0 {
75+ & [ ]
76+ } else {
77+ unsafe { std:: slice:: from_raw_parts ( export. ptr , export. len ) }
78+ }
79+ }
80+ }
81+
82+ impl Drop for BufferExportGuard {
83+ fn drop ( & mut self ) {
84+ // The producer's release callback owns cleanup of both private data and the descriptor.
85+ let export = unsafe { self . export . as_ref ( ) } ;
86+ if let Some ( release) = export. release {
87+ unsafe { release ( self . export . as_ptr ( ) ) } ;
88+ }
89+ }
90+ }
91+
92+ // The guard is moved into `Bytes::from_owner`, which requires `Send + Sync`. After import we disable
93+ // the source capsule destructor and own the C export until this guard is dropped.
94+ unsafe impl Send for BufferExportGuard { }
95+ unsafe impl Sync for BufferExportGuard { }
96+
97+ fn import_buffer_from_capsule ( capsule : & Bound < ' _ , PyCapsule > ) -> PyResult < BufferHandle > {
98+ let export_ptr = capsule
99+ . pointer_checked ( Some ( BUFFER_EXPORT_CAPSULE_NAME ) ) ?
100+ . cast :: < VortexBufferExport > ( ) ;
101+ let export = unsafe { export_ptr. as_ref ( ) } ;
102+
103+ if export. version != VORTEX_BUFFER_EXPORT_VERSION {
104+ return Err ( PyValueError :: new_err ( format ! (
105+ "unsupported VortexBufferExport version {}" ,
106+ export. version
107+ ) ) ) ;
108+ }
109+ if export. kind != VORTEX_BUFFER_HOST {
110+ return Err ( PyValueError :: new_err ( format ! (
111+ "unsupported buffer kind {} (only host buffers are supported in metadata bridge)" ,
112+ export. kind
113+ ) ) ) ;
114+ }
115+
116+ if export. len != 0 && export. ptr . is_null ( ) {
117+ return Err ( PyValueError :: new_err (
118+ "non-empty VortexBufferExport has null data pointer" ,
119+ ) ) ;
120+ }
121+ if export. release . is_none ( ) {
122+ return Err ( PyValueError :: new_err (
123+ "VortexBufferExport is missing a release callback" ,
124+ ) ) ;
125+ }
126+
127+ let len = export. len ;
128+ let alignment = vortex:: buffer:: Alignment :: try_from (
129+ u32:: try_from ( export. alignment )
130+ . map_err ( |_| PyValueError :: new_err ( "buffer alignment exceeds u32" ) ) ?,
131+ )
132+ . map_err ( |e| PyValueError :: new_err ( e. to_string ( ) ) ) ?;
133+
134+ if len != 0 && !alignment. is_ptr_aligned ( export. ptr ) {
135+ return Err ( PyValueError :: new_err ( format ! (
136+ "buffer pointer is not aligned to requested alignment {alignment}"
137+ ) ) ) ;
138+ }
139+
140+ // Transfer ownership of the boxed VortexBufferExport from the producer capsule into the Bytes
141+ // owner below. Otherwise the producer capsule could be dropped before the reconstructed
142+ // BufferHandle, leaving the Bytes owner with a dangling export pointer.
143+ unsafe { ffi:: PyCapsule_SetDestructor ( capsule. as_ptr ( ) , None ) } ;
144+ if PyErr :: occurred ( capsule. py ( ) ) {
145+ return Err ( PyErr :: fetch ( capsule. py ( ) ) ) ;
146+ }
147+
148+ let guard = BufferExportGuard { export : export_ptr } ;
149+
150+ let byte_buffer = if len == 0 {
151+ drop ( guard) ;
152+ ByteBuffer :: empty_aligned ( alignment)
153+ } else {
154+ ByteBuffer :: from ( bytes:: Bytes :: from_owner ( guard) ) . aligned ( alignment)
155+ } ;
156+
157+ Ok ( BufferHandle :: new_host ( byte_buffer) )
158+ }
159+
58160struct ExportedDeviceArray ( ArrowDeviceArrayWithSchema ) ;
59161
60162// The exported Arrow C Device structs own CPU-side metadata plus CUDA device pointers through their
@@ -101,7 +203,7 @@ struct ArrayMetadata {
101203 dtype : Vec < u8 > ,
102204 len : usize ,
103205 metadata : Vec < u8 > ,
104- buffer_count : usize ,
206+ buffers : Vec < BufferHandle > ,
105207 children : Vec < ArrayMetadata > ,
106208}
107209
@@ -147,6 +249,16 @@ fn parse_array_metadata(value: &Bound<'_, PyAny>) -> PyResult<ArrayMetadata> {
147249 ) ) ) ;
148250 }
149251
252+ let buffers = tuple
253+ . get_item ( 4 ) ?
254+ . cast :: < PyList > ( ) ?
255+ . iter ( )
256+ . map ( |item| {
257+ let capsule: Bound < ' _ , PyCapsule > = item. extract ( ) ?;
258+ import_buffer_from_capsule ( & capsule)
259+ } )
260+ . collect :: < PyResult < Vec < _ > > > ( ) ?;
261+
150262 let children = tuple
151263 . get_item ( 5 ) ?
152264 . cast :: < PyList > ( ) ?
@@ -159,7 +271,7 @@ fn parse_array_metadata(value: &Bound<'_, PyAny>) -> PyResult<ArrayMetadata> {
159271 dtype : tuple. get_item ( 1 ) ?. extract ( ) ?,
160272 len : tuple. get_item ( 2 ) ?. extract ( ) ?,
161273 metadata : tuple. get_item ( 3 ) ?. extract ( ) ?,
162- buffer_count : tuple . get_item ( 4 ) ? . extract ( ) ? ,
274+ buffers ,
163275 children,
164276 } )
165277}
@@ -173,14 +285,6 @@ fn deserialize_metadata_tree(
173285 metadata : & ArrayMetadata ,
174286 session : & VortexSession ,
175287) -> VortexResult < ArrayRef > {
176- if metadata. buffer_count != 0 {
177- vortex_bail ! (
178- "metadata-only bridge cannot deserialize array {} with {} buffers yet" ,
179- metadata. encoding_id,
180- metadata. buffer_count
181- ) ;
182- }
183-
184288 let dtype = dtype_from_metadata ( metadata, session) ?;
185289 let children = metadata
186290 . children
@@ -194,12 +298,11 @@ fn deserialize_metadata_tree(
194298 . registry ( )
195299 . find ( & encoding_id)
196300 . ok_or_else ( || vortex_err ! ( "Unknown array encoding: {}" , metadata. encoding_id) ) ?;
197- let buffers: & [ BufferHandle ] = & [ ] ;
198301 let decoded = plugin. deserialize (
199302 & dtype,
200303 metadata. len ,
201304 & metadata. metadata ,
202- buffers,
305+ & metadata . buffers ,
203306 & children,
204307 session,
205308 ) ?;
@@ -246,6 +349,14 @@ fn _debug_array_metadata_dtype(array: Bound<'_, PyAny>) -> PyResult<String> {
246349 Ok ( array. dtype ( ) . to_string ( ) )
247350}
248351
352+ /// Return array values after crossing the private vtable-metadata bridge.
353+ #[ pyfunction]
354+ fn _debug_array_metadata_display_values ( array : Bound < ' _ , PyAny > ) -> PyResult < String > {
355+ let metadata = extract_array_metadata ( & array) ?;
356+ let array = deserialize_metadata_tree ( & metadata, & METADATA_SESSION ) . map_err ( to_py_err) ?;
357+ Ok ( array. display_values ( ) . to_string ( ) )
358+ }
359+
249360/// Export a PyVortex array as Arrow C Device schema and array PyCapsules.
250361#[ pyfunction]
251362#[ pyo3( signature = ( array, requested_schema = None , * * kwargs) ) ]
@@ -461,6 +572,7 @@ unsafe extern "C" fn release_device_array_capsule(capsule: *mut ffi::PyObject) {
461572fn _lib ( m : & Bound < PyModule > ) -> PyResult < ( ) > {
462573 m. add_function ( wrap_pyfunction ! ( cuda_available, m) ?) ?;
463574 m. add_function ( wrap_pyfunction ! ( _debug_array_metadata_dtype, m) ?) ?;
575+ m. add_function ( wrap_pyfunction ! ( _debug_array_metadata_display_values, m) ?) ?;
464576 m. add_function ( wrap_pyfunction ! ( export_device_array, m) ?) ?;
465577 Ok ( ( ) )
466578}
0 commit comments