From cd6d404a1bbb6935b99eece2df1f9af7cb26cb45 Mon Sep 17 00:00:00 2001 From: Alaric Senat Date: Tue, 25 Aug 2026 11:14:48 +0200 Subject: [PATCH 1/2] libvlc-sys: re-generate the bindings The previous bindings were built on an arbitrary host (my machine) rather than the stable MSRV container we use for the CI. --- libvlc-sys/bindings.rs | 206 ++--------------------------------------- 1 file changed, 6 insertions(+), 200 deletions(-) diff --git a/libvlc-sys/bindings.rs b/libvlc-sys/bindings.rs index baef517..baf1397 100644 --- a/libvlc-sys/bindings.rs +++ b/libvlc-sys/bindings.rs @@ -1,145 +1,5 @@ /* automatically generated by rust-bindgen 0.72.1 */ -#[repr(C)] -#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct __BindgenBitfieldUnit { - storage: Storage, -} -impl __BindgenBitfieldUnit { - #[inline] - pub const fn new(storage: Storage) -> Self { - Self { storage } - } -} -impl __BindgenBitfieldUnit -where - Storage: AsRef<[u8]> + AsMut<[u8]>, -{ - #[inline] - fn extract_bit(byte: u8, index: usize) -> bool { - let bit_index = if cfg!(target_endian = "big") { - 7 - (index % 8) - } else { - index % 8 - }; - let mask = 1 << bit_index; - byte & mask == mask - } - #[inline] - pub fn get_bit(&self, index: usize) -> bool { - debug_assert!(index / 8 < self.storage.as_ref().len()); - let byte_index = index / 8; - let byte = self.storage.as_ref()[byte_index]; - Self::extract_bit(byte, index) - } - #[inline] - pub unsafe fn raw_get_bit(this: *const Self, index: usize) -> bool { - debug_assert!(index / 8 < core::mem::size_of::()); - let byte_index = index / 8; - let byte = unsafe { - *(core::ptr::addr_of!((*this).storage) as *const u8).offset(byte_index as isize) - }; - Self::extract_bit(byte, index) - } - #[inline] - fn change_bit(byte: u8, index: usize, val: bool) -> u8 { - let bit_index = if cfg!(target_endian = "big") { - 7 - (index % 8) - } else { - index % 8 - }; - let mask = 1 << bit_index; - if val { - byte | mask - } else { - byte & !mask - } - } - #[inline] - pub fn set_bit(&mut self, index: usize, val: bool) { - debug_assert!(index / 8 < self.storage.as_ref().len()); - let byte_index = index / 8; - let byte = &mut self.storage.as_mut()[byte_index]; - *byte = Self::change_bit(*byte, index, val); - } - #[inline] - pub unsafe fn raw_set_bit(this: *mut Self, index: usize, val: bool) { - debug_assert!(index / 8 < core::mem::size_of::()); - let byte_index = index / 8; - let byte = unsafe { - (core::ptr::addr_of_mut!((*this).storage) as *mut u8).offset(byte_index as isize) - }; - unsafe { *byte = Self::change_bit(*byte, index, val) }; - } - #[inline] - pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { - debug_assert!(bit_width <= 64); - debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); - debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); - let mut val = 0; - for i in 0..(bit_width as usize) { - if self.get_bit(i + bit_offset) { - let index = if cfg!(target_endian = "big") { - bit_width as usize - 1 - i - } else { - i - }; - val |= 1 << index; - } - } - val - } - #[inline] - pub unsafe fn raw_get(this: *const Self, bit_offset: usize, bit_width: u8) -> u64 { - debug_assert!(bit_width <= 64); - debug_assert!(bit_offset / 8 < core::mem::size_of::()); - debug_assert!((bit_offset + (bit_width as usize)) / 8 <= core::mem::size_of::()); - let mut val = 0; - for i in 0..(bit_width as usize) { - if unsafe { Self::raw_get_bit(this, i + bit_offset) } { - let index = if cfg!(target_endian = "big") { - bit_width as usize - 1 - i - } else { - i - }; - val |= 1 << index; - } - } - val - } - #[inline] - pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { - debug_assert!(bit_width <= 64); - debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); - debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); - for i in 0..(bit_width as usize) { - let mask = 1 << i; - let val_bit_is_set = val & mask == mask; - let index = if cfg!(target_endian = "big") { - bit_width as usize - 1 - i - } else { - i - }; - self.set_bit(index + bit_offset, val_bit_is_set); - } - } - #[inline] - pub unsafe fn raw_set(this: *mut Self, bit_offset: usize, bit_width: u8, val: u64) { - debug_assert!(bit_width <= 64); - debug_assert!(bit_offset / 8 < core::mem::size_of::()); - debug_assert!((bit_offset + (bit_width as usize)) / 8 <= core::mem::size_of::()); - for i in 0..(bit_width as usize) { - let mask = 1 << i; - let val_bit_is_set = val & mask == mask; - let index = if cfg!(target_endian = "big") { - bit_width as usize - 1 - i - } else { - i - }; - unsafe { Self::raw_set_bit(this, index + bit_offset, val_bit_is_set) }; - } - } -} pub const VLC_VLC_H: u32 = 1; pub const VLC_LIBVLC_H: u32 = 1; pub const VLC_LIBVLC_RENDERER_DISCOVERER_H: u32 = 1; @@ -156,7 +16,6 @@ pub const LIBVLC_DIALOG_H: u32 = 1; pub const LIBVLC_VLM_H: u32 = 1; pub const LIBVLC_DEPRECATED_H: u32 = 1; pub type __gnuc_va_list = __builtin_va_list; -pub type __uint64_t = libc::c_ulong; pub type __off_t = libc::c_long; pub type __off64_t = libc::c_long; pub type FILE = _IO_FILE; @@ -194,9 +53,7 @@ pub struct _IO_FILE { pub _markers: *mut _IO_marker, pub _chain: *mut _IO_FILE, pub _fileno: libc::c_int, - pub _bitfield_align_1: [u32; 0], - pub _bitfield_1: __BindgenBitfieldUnit<[u8; 3usize]>, - pub _short_backupbuf: [libc::c_char; 1usize], + pub _flags2: libc::c_int, pub _old_offset: __off_t, pub _cur_column: libc::c_ushort, pub _vtable_offset: libc::c_schar, @@ -207,11 +64,9 @@ pub struct _IO_FILE { pub _wide_data: *mut _IO_wide_data, pub _freeres_list: *mut _IO_FILE, pub _freeres_buf: *mut libc::c_void, - pub _prevchain: *mut *mut _IO_FILE, + pub __pad5: usize, pub _mode: libc::c_int, - pub _unused3: libc::c_int, - pub _total_written: __uint64_t, - pub _unused2: [libc::c_char; 8usize], + pub _unused2: [libc::c_char; 20usize], } #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { @@ -243,8 +98,7 @@ const _: () = { ["Offset of field: _IO_FILE::_markers"][::core::mem::offset_of!(_IO_FILE, _markers) - 96usize]; ["Offset of field: _IO_FILE::_chain"][::core::mem::offset_of!(_IO_FILE, _chain) - 104usize]; ["Offset of field: _IO_FILE::_fileno"][::core::mem::offset_of!(_IO_FILE, _fileno) - 112usize]; - ["Offset of field: _IO_FILE::_short_backupbuf"] - [::core::mem::offset_of!(_IO_FILE, _short_backupbuf) - 119usize]; + ["Offset of field: _IO_FILE::_flags2"][::core::mem::offset_of!(_IO_FILE, _flags2) - 116usize]; ["Offset of field: _IO_FILE::_old_offset"] [::core::mem::offset_of!(_IO_FILE, _old_offset) - 120usize]; ["Offset of field: _IO_FILE::_cur_column"] @@ -262,58 +116,10 @@ const _: () = { [::core::mem::offset_of!(_IO_FILE, _freeres_list) - 168usize]; ["Offset of field: _IO_FILE::_freeres_buf"] [::core::mem::offset_of!(_IO_FILE, _freeres_buf) - 176usize]; - ["Offset of field: _IO_FILE::_prevchain"] - [::core::mem::offset_of!(_IO_FILE, _prevchain) - 184usize]; + ["Offset of field: _IO_FILE::__pad5"][::core::mem::offset_of!(_IO_FILE, __pad5) - 184usize]; ["Offset of field: _IO_FILE::_mode"][::core::mem::offset_of!(_IO_FILE, _mode) - 192usize]; - ["Offset of field: _IO_FILE::_unused3"][::core::mem::offset_of!(_IO_FILE, _unused3) - 196usize]; - ["Offset of field: _IO_FILE::_total_written"] - [::core::mem::offset_of!(_IO_FILE, _total_written) - 200usize]; - ["Offset of field: _IO_FILE::_unused2"][::core::mem::offset_of!(_IO_FILE, _unused2) - 208usize]; + ["Offset of field: _IO_FILE::_unused2"][::core::mem::offset_of!(_IO_FILE, _unused2) - 196usize]; }; -impl _IO_FILE { - #[inline] - pub fn _flags2(&self) -> libc::c_int { - unsafe { ::core::mem::transmute(self._bitfield_1.get(0usize, 24u8) as u32) } - } - #[inline] - pub fn set__flags2(&mut self, val: libc::c_int) { - unsafe { - let val: u32 = ::core::mem::transmute(val); - self._bitfield_1.set(0usize, 24u8, val as u64) - } - } - #[inline] - pub unsafe fn _flags2_raw(this: *const Self) -> libc::c_int { - unsafe { - ::core::mem::transmute(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get( - ::core::ptr::addr_of!((*this)._bitfield_1), - 0usize, - 24u8, - ) as u32) - } - } - #[inline] - pub unsafe fn set__flags2_raw(this: *mut Self, val: libc::c_int) { - unsafe { - let val: u32 = ::core::mem::transmute(val); - <__BindgenBitfieldUnit<[u8; 3usize]>>::raw_set( - ::core::ptr::addr_of_mut!((*this)._bitfield_1), - 0usize, - 24u8, - val as u64, - ) - } - } - #[inline] - pub fn new_bitfield_1(_flags2: libc::c_int) -> __BindgenBitfieldUnit<[u8; 3usize]> { - let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 3usize]> = Default::default(); - __bindgen_bitfield_unit.set(0usize, 24u8, { - let _flags2: u32 = unsafe { ::core::mem::transmute(_flags2) }; - _flags2 as u64 - }); - __bindgen_bitfield_unit - } -} pub type va_list = __gnuc_va_list; unsafe extern "C" { pub fn vsnprintf( From 90729e2bd431182bb70eeb39ca4e2237de4ef675 Mon Sep 17 00:00:00 2001 From: Alaric Senat Date: Mon, 24 Aug 2026 23:09:01 +0200 Subject: [PATCH 2/2] libvlc-sys: fix cross-platform va_list layout bindgen spells the log callback's va_list parameter the way the host that ran it does. The bindings are generated once and compiled everywhere, so that spelling only happens to be right on the generating host. This was alright for most hosts but is fragile on aarch64 where va_list is a plain 32 bytes struct. Given we only forward the list to vsnprintf, extracting and simplifying the cross-platform rust implementation from the unstable std-lib is cheap and sound. We will just have to re-export the official type once it's stablilized. --- libvlc-sys/bindings.rs | 32 +++++--------------------------- libvlc-sys/src/lib.rs | 2 ++ libvlc-sys/src/valist.rs | 29 +++++++++++++++++++++++++++++ src/core.rs | 2 +- xtask/src/main.rs | 14 +++++++++++++- 5 files changed, 50 insertions(+), 29 deletions(-) create mode 100644 libvlc-sys/src/valist.rs diff --git a/libvlc-sys/bindings.rs b/libvlc-sys/bindings.rs index baf1397..08ec9d6 100644 --- a/libvlc-sys/bindings.rs +++ b/libvlc-sys/bindings.rs @@ -1,5 +1,7 @@ /* automatically generated by rust-bindgen 0.72.1 */ +pub use crate::valist::VaList; + pub const VLC_VLC_H: u32 = 1; pub const VLC_LIBVLC_H: u32 = 1; pub const VLC_LIBVLC_RENDERER_DISCOVERER_H: u32 = 1; @@ -15,7 +17,6 @@ pub const LIBVLC_EVENTS_H: u32 = 1; pub const LIBVLC_DIALOG_H: u32 = 1; pub const LIBVLC_VLM_H: u32 = 1; pub const LIBVLC_DEPRECATED_H: u32 = 1; -pub type __gnuc_va_list = __builtin_va_list; pub type __off_t = libc::c_long; pub type __off64_t = libc::c_long; pub type FILE = _IO_FILE; @@ -120,13 +121,12 @@ const _: () = { ["Offset of field: _IO_FILE::_mode"][::core::mem::offset_of!(_IO_FILE, _mode) - 192usize]; ["Offset of field: _IO_FILE::_unused2"][::core::mem::offset_of!(_IO_FILE, _unused2) - 196usize]; }; -pub type va_list = __gnuc_va_list; unsafe extern "C" { pub fn vsnprintf( __s: *mut libc::c_char, __maxlen: libc::c_ulong, __format: *const libc::c_char, - __arg: *mut __va_list_tag, + __arg: VaList, ) -> libc::c_int; } #[repr(C)] @@ -144,7 +144,7 @@ unsafe extern "C" { unsafe extern "C" { pub fn libvlc_vprinterr( fmt: *const libc::c_char, - ap: *mut __va_list_tag, + ap: VaList, ) -> *const libc::c_char; } unsafe extern "C" { @@ -263,7 +263,7 @@ pub type libvlc_log_cb = ::core::option::Option< level: libc::c_int, ctx: *const libvlc_log_t, fmt: *const libc::c_char, - args: *mut __va_list_tag, + args: VaList, ), >; unsafe extern "C" { @@ -3210,25 +3210,3 @@ unsafe extern "C" { ppsz_options: *mut *mut libc::c_char, ); } -pub type __builtin_va_list = [__va_list_tag; 1usize]; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct __va_list_tag { - pub gp_offset: libc::c_uint, - pub fp_offset: libc::c_uint, - pub overflow_arg_area: *mut libc::c_void, - pub reg_save_area: *mut libc::c_void, -} -#[allow(clippy::unnecessary_operation, clippy::identity_op)] -const _: () = { - ["Size of __va_list_tag"][::core::mem::size_of::<__va_list_tag>() - 24usize]; - ["Alignment of __va_list_tag"][::core::mem::align_of::<__va_list_tag>() - 8usize]; - ["Offset of field: __va_list_tag::gp_offset"] - [::core::mem::offset_of!(__va_list_tag, gp_offset) - 0usize]; - ["Offset of field: __va_list_tag::fp_offset"] - [::core::mem::offset_of!(__va_list_tag, fp_offset) - 4usize]; - ["Offset of field: __va_list_tag::overflow_arg_area"] - [::core::mem::offset_of!(__va_list_tag, overflow_arg_area) - 8usize]; - ["Offset of field: __va_list_tag::reg_save_area"] - [::core::mem::offset_of!(__va_list_tag, reg_save_area) - 16usize]; -}; diff --git a/libvlc-sys/src/lib.rs b/libvlc-sys/src/lib.rs index 12b7d82..e7ec8c3 100644 --- a/libvlc-sys/src/lib.rs +++ b/libvlc-sys/src/lib.rs @@ -3,6 +3,8 @@ #![allow(non_snake_case)] #![no_std] +pub mod valist; + // The bindings are a committed source file, regenerated out of band with // `cargo xtask bindgen`. include!("../bindings.rs"); diff --git a/libvlc-sys/src/valist.rs b/libvlc-sys/src/valist.rs new file mode 100644 index 0000000..ab7bd0e --- /dev/null +++ b/libvlc-sys/src/valist.rs @@ -0,0 +1,29 @@ +//! Platform `va_list`, extracted and simplified from `core::ffi::va_list`. +//! +//! libvlc-sys rust bindings are common to all libvlc's supported platforms and are bindgen +//! generated once by the maintainers. Hence it needs a cross-platform va_list type. +//! +//! VaLists are platform specific, the C standard only specifies that they should be possible to +//! pass by value. Libvlc uses them in the logger callback exclusively, until the rust stdlib C +//! variadic API is stable, the type with the single goal of being forwarded to a C formatting +//! function. Which simplifies greatly the implementation. +//! +//! Once the c_variadic API is stabilized, this file should be removed. + +/// [AArch64 Procedure Call Standard]: +/// https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#id110 +#[cfg(all(target_arch = "aarch64", not(target_vendor = "apple")))] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct VaList { + stack: *mut libc::c_void, + gr_top: *mut libc::c_void, + vr_top: *mut libc::c_void, + gr_offs: i32, + vr_offs: i32, +} + +/// Everywhere else: either an opaque pointer already, or a pointer to the struct the array decayed +/// into. This is sound since VaLists are only passed around. +#[cfg(not(all(target_arch = "aarch64", not(target_vendor = "apple"))))] +pub type VaList = *mut libc::c_void; diff --git a/src/core.rs b/src/core.rs index 1b4094b..dd4ed94 100644 --- a/src/core.rs +++ b/src/core.rs @@ -156,7 +156,7 @@ impl Drop for Instance { const BUF_SIZE: usize = 1024; // Write log message to the buffer by vsnprintf. unsafe extern "C" fn logging_cb( - data: *mut c_void, level: c_int, ctx: *const sys::libvlc_log_t, fmt: *const c_char, args: *mut sys::__va_list_tag) { + data: *mut c_void, level: c_int, ctx: *const sys::libvlc_log_t, fmt: *const c_char, args: sys::VaList) { let f: &Box) + Send + 'static> = ::std::mem::transmute(data); let mut buf: [c_char; BUF_SIZE] = [0; BUF_SIZE]; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 6699e2b..012e273 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -45,7 +45,11 @@ fn generate_bindings() { .allowlist_item("(lib|LIB)?(vlc|VLC)_.*") // Required by the Windows `legacy_stdio_definitions` link workaround // (see libvlc-sys/build.rs). - .allowlist_function("vsnprintf"); + .allowlist_function("vsnprintf") + // Block the whole va_list family and rewrite the parameters bindings to our own `VaList`, + // which is ABI-correct on every target. + .blocklist_type(".*va_list.*") + .raw_line("pub use crate::valist::VaList;"); for path in &library.include_paths { bindings = bindings.clang_arg(format!("-I{}", path.display())); @@ -56,6 +60,14 @@ fn generate_bindings() { .expect("unable to generate bindings") .to_string(); + let generated = generated + .replace("*mut __va_list_tag", "VaList") + .replace(": va_list", ": VaList"); + assert!( + !generated.contains("va_list"), + "a va_list spelling we do not know about survived" + ); + std::fs::write(&output, generated).expect("couldn't write bindings"); println!("wrote {}", output.display()); }