Input C/C++ header
#include <stdint.h>
typedef union __attribute__((transparent_union)) U {
signed char first;
signed char second;
} U;
int32_t tu_capture(U value);
Bindgen invocation
$ bindgen input.h \
--allowlist-type '^U$' \
--allowlist-function '^tu_.*' \
--no-layout-tests \
--rust-target 1.75 \
--rust-edition 2021 \
-- -std=gnu11
Actual output
#[repr(C)]
#[derive(Copy, Clone)]
pub union U {
pub first: ::std::os::raw::c_schar,
pub second: ::std::os::raw::c_schar,
}
extern "C" {
pub fn tu_capture(value: U) -> i32;
}
Clang applies the calling convention of the first union member:
define dso_local i32 @tu_capture(%union.U noundef signext %0)
The generated Rust declaration is lowered without signext:
declare noundef i32 @tu_capture(i8)
I used this small x86_64 ABI observer as the implementation:
#include "input.h"
__attribute__((naked, noinline))
int32_t tu_capture(U value __attribute__((unused))) {
__asm__("movl %edi, %eax\n\t"
"retq");
}
Calling it from C with .first = -1 produces:
Calling the same object through the generated binding produces:
The results are the same at -O0 and -O2.
What is wrong
A transparent_union parameter uses the calling convention of its first
member. Here that member is signed char, so the caller must sign-extend -1
to 0xffffffff. Bindgen emits an ordinary #[repr(C)] union parameter
instead. Rust lowers it as an i8 argument without signext, so the caller
places 0x000000ff in EDI.
The union itself has the correct size and alignment. The mismatch is in the
function parameter ABI.
Expected output
For this declaration, lowering the parameter to the first member type gives an
ABI-correct Rust declaration:
extern "C" {
pub fn tu_capture(value: ::std::os::raw::c_schar) -> i32;
}
Rust lowers that parameter with signext. If bindgen cannot represent
transparent_union parameters, it should diagnose or omit the affected
function instead of emitting an ordinary union parameter with a different ABI.
Impact
The generated binding compiles and links, but a signed char value of -1
reaches the callee as 255. This reproduces with current main and bindgen
0.72.1.
Environment
bindgen: 0.72.0, current main 25b23474496e78f6a1fbf1c02cb66f11e4d176d0
bindgen release: 0.72.1
clang/libclang: 15.0.7
rustc: 1.75.0
target: x86_64-unknown-linux-gnu
OS: Ubuntu 22.04.5 LTS, x86_64
Input C/C++ header
Bindgen invocation
Actual output
Clang applies the calling convention of the first union member:
The generated Rust declaration is lowered without
signext:I used this small x86_64 ABI observer as the implementation:
Calling it from C with
.first = -1produces:Calling the same object through the generated binding produces:
The results are the same at
-O0and-O2.What is wrong
A
transparent_unionparameter uses the calling convention of its firstmember. Here that member is
signed char, so the caller must sign-extend-1to
0xffffffff. Bindgen emits an ordinary#[repr(C)]union parameterinstead. Rust lowers it as an
i8argument withoutsignext, so the callerplaces
0x000000ffin EDI.The union itself has the correct size and alignment. The mismatch is in the
function parameter ABI.
Expected output
For this declaration, lowering the parameter to the first member type gives an
ABI-correct Rust declaration:
Rust lowers that parameter with
signext. If bindgen cannot representtransparent_unionparameters, it should diagnose or omit the affectedfunction instead of emitting an ordinary union parameter with a different ABI.
Impact
The generated binding compiles and links, but a
signed charvalue of-1reaches the callee as
255. This reproduces with current main and bindgen0.72.1.
Environment