From b8ba4002f5d3d71be024e4d0ff39913e887ec510 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 22 Feb 2026 17:32:24 +0100 Subject: [PATCH] c-variadic: handle c_int being i16 and c_double being f32 on avr --- compiler/rustc_codegen_llvm/src/intrinsic.rs | 68 +++++++++++-------- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 10 ++- library/core/src/ffi/va_list.rs | 48 ++++++++++++- .../c-link-to-rust-va-list-fn/checkrust.rs | 12 ++-- .../run-make/c-link-to-rust-va-list-fn/test.c | 2 +- 5 files changed, 99 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 0d3d682ece21..9742f9fb3e42 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -285,37 +285,47 @@ fn codegen_intrinsic_call( } sym::breakpoint => self.call_intrinsic("llvm.debugtrap", &[], &[]), sym::va_arg => { - match result.layout.backend_repr { - BackendRepr::Scalar(scalar) => { - match scalar.primitive() { - Primitive::Int(..) => { - if self.cx().size_of(result.layout.ty).bytes() < 4 { - // `va_arg` should not be called on an integer type - // less than 4 bytes in length. If it is, promote - // the integer to an `i32` and truncate the result - // back to the smaller type. - let promoted_result = emit_va_arg(self, args[0], tcx.types.i32); - self.trunc(promoted_result, result.layout.llvm_type(self)) - } else { - emit_va_arg(self, args[0], result.layout.ty) - } - } - Primitive::Float(Float::F16) => { - bug!("the va_arg intrinsic does not work with `f16`") - } - Primitive::Float(Float::F64) | Primitive::Pointer(_) => { - emit_va_arg(self, args[0], result.layout.ty) - } - // `va_arg` should never be used with the return type f32. - Primitive::Float(Float::F32) => { - bug!("the va_arg intrinsic does not work with `f32`") - } - Primitive::Float(Float::F128) => { - bug!("the va_arg intrinsic does not work with `f128`") - } + let BackendRepr::Scalar(scalar) = result.layout.backend_repr else { + bug!("the va_arg intrinsic does not support non-scalar types") + }; + + match scalar.primitive() { + Primitive::Pointer(_) => { + // Pointers are always OK. + emit_va_arg(self, args[0], result.layout.ty) + } + Primitive::Int(..) => { + let int_width = self.cx().size_of(result.layout.ty).bits(); + let target_c_int_width = self.cx().sess().target.options.c_int_width; + if int_width < u64::from(target_c_int_width) { + // Smaller integer types are automatically promototed and `va_arg` + // should not be called on them. + bug!( + "va_arg got i{} but needs at least c_int (an i{})", + int_width, + target_c_int_width + ); + } + emit_va_arg(self, args[0], result.layout.ty) + } + Primitive::Float(Float::F16) => { + bug!("the va_arg intrinsic does not support `f16`") + } + Primitive::Float(Float::F32) => { + if self.cx().sess().target.arch == Arch::Avr { + // c_double is actually f32 on avr. + emit_va_arg(self, args[0], result.layout.ty) + } else { + bug!("the va_arg intrinsic does not support `f32` on this target") } } - _ => bug!("the va_arg intrinsic does not work with non-scalar types"), + Primitive::Float(Float::F64) => { + // 64-bit floats are always OK. + emit_va_arg(self, args[0], result.layout.ty) + } + Primitive::Float(Float::F128) => { + bug!("the va_arg intrinsic does not support `f128`") + } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index bb31bcbf70f1..966f02068631 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -499,10 +499,16 @@ fn variadic_error<'tcx>( ty::Float(ty::FloatTy::F32) => { variadic_error(tcx.sess, arg.span, arg_ty, "c_double"); } - ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Bool => { + ty::Int(ty::IntTy::I8) | ty::Bool => { variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); } - ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) => { + ty::Uint(ty::UintTy::U8) => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); + } + ty::Int(ty::IntTy::I16) if tcx.sess.target.options.c_int_width > 16 => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); + } + ty::Uint(ty::UintTy::U16) if tcx.sess.target.options.c_int_width > 16 => { variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); } ty::FnDef(..) => { diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index f0f58a0f8343..45e25fabe3bd 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -266,14 +266,17 @@ fn drop(&mut self) { mod sealed { pub trait Sealed {} + impl Sealed for i16 {} impl Sealed for i32 {} impl Sealed for i64 {} impl Sealed for isize {} + impl Sealed for u16 {} impl Sealed for u32 {} impl Sealed for u64 {} impl Sealed for usize {} + impl Sealed for f32 {} impl Sealed for f64 {} impl Sealed for *mut T {} @@ -299,22 +302,61 @@ impl Sealed for *const T {} // to accept unsupported types in the meantime. pub unsafe trait VaArgSafe: sealed::Sealed {} -// i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. +crate::cfg_select! { + any(target_arch = "avr", target_arch = "msp430") => { + // c_int/c_uint are i16/u16 on these targets. + // + // - i8 is implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. + // - u8 is implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`. + unsafe impl VaArgSafe for i16 {} + unsafe impl VaArgSafe for u16 {} + } + _ => { + // c_int/c_uint are i32/u32 on this target. + // + // - i8 and i16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. + // - u8 and u16 are implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`. + } +} + +crate::cfg_select! { + target_arch = "avr" => { + // c_double is f32 on this target. + unsafe impl VaArgSafe for f32 {} + } + _ => { + // c_double is f64 on this target. + // + // - f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`. + } +} + unsafe impl VaArgSafe for i32 {} unsafe impl VaArgSafe for i64 {} unsafe impl VaArgSafe for isize {} -// u8 and u16 are implicitly promoted to c_int in C, and cannot implement `VaArgSafe`. unsafe impl VaArgSafe for u32 {} unsafe impl VaArgSafe for u64 {} unsafe impl VaArgSafe for usize {} -// f32 is implicitly promoted to c_double in C, and cannot implement `VaArgSafe`. unsafe impl VaArgSafe for f64 {} unsafe impl VaArgSafe for *mut T {} unsafe impl VaArgSafe for *const T {} +// Check that relevant `core::ffi` types implement `VaArgSafe`. +const _: () = { + const fn va_arg_safe_check() {} + + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); + va_arg_safe_check::(); +}; + impl<'f> VaList<'f> { /// Read an argument from the variable argument list, and advance to the next argument. /// diff --git a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs index c522ac46d918..109fbb1c6203 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +++ b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs @@ -30,17 +30,17 @@ unsafe fn compare_c_str(ptr: *const c_char, val: &CStr) -> bool { continue_if!(ap.arg::() == '4' as c_int); continue_if!(ap.arg::() == ';' as c_int); continue_if!(ap.arg::() == 0x32); - continue_if!(ap.arg::() == 0x10000001); + continue_if!(ap.arg::() == 0x10000001); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"Valid!")); 0 } #[unsafe(no_mangle)] pub unsafe extern "C" fn check_list_2(mut ap: VaList) -> usize { - continue_if!(ap.arg::() == 3.14f64); + continue_if!(ap.arg::() == 3.14); continue_if!(ap.arg::() == 12); continue_if!(ap.arg::() == 'a' as c_int); - continue_if!(ap.arg::() == 6.28f64); + continue_if!(ap.arg::() == 6.28); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"Hello")); continue_if!(ap.arg::() == 42); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"World")); @@ -49,7 +49,7 @@ unsafe fn compare_c_str(ptr: *const c_char, val: &CStr) -> bool { #[unsafe(no_mangle)] pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize { - continue_if!(ap.arg::() == 6.28f64); + continue_if!(ap.arg::() == 6.28); continue_if!(ap.arg::() == 16); continue_if!(ap.arg::() == 'A' as c_int); continue_if!(compare_c_str(ap.arg::<*const c_char>(), c"Skip Me!")); @@ -66,7 +66,7 @@ unsafe fn compare_c_str(ptr: *const c_char, val: &CStr) -> bool { #[unsafe(no_mangle)] pub unsafe extern "C" fn check_varargs_1(_: c_int, mut ap: ...) -> usize { - continue_if!(ap.arg::() == 3.14f64); + continue_if!(ap.arg::() == 3.14); continue_if!(ap.arg::() == 12); continue_if!(ap.arg::() == 'A' as c_int); continue_if!(ap.arg::() == 1); @@ -156,7 +156,7 @@ extern "C" fn run_test_variadic() -> usize { #[unsafe(no_mangle)] extern "C" fn run_test_va_list_by_value() -> usize { - unsafe extern "C" fn helper(mut ap: ...) -> usize { + unsafe extern "C" fn helper(ap: ...) -> usize { unsafe { test_va_list_by_value(ap) } } diff --git a/tests/run-make/c-link-to-rust-va-list-fn/test.c b/tests/run-make/c-link-to-rust-va-list-fn/test.c index 2bb93c0b5d0e..b368302326c7 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/test.c +++ b/tests/run-make/c-link-to-rust-va-list-fn/test.c @@ -32,7 +32,7 @@ int test_rust(size_t (*fn)(va_list), ...) { int main(int argc, char* argv[]) { assert(test_rust(check_list_0, 0x01LL, 0x02, 0x03LL) == 0); - assert(test_rust(check_list_1, -1, 'A', '4', ';', 0x32, 0x10000001, "Valid!") == 0); + assert(test_rust(check_list_1, -1, 'A', '4', ';', 0x32, (int32_t)0x10000001, "Valid!") == 0); assert(test_rust(check_list_2, 3.14, 12l, 'a', 6.28, "Hello", 42, "World") == 0);