Skip to content

Commit 1b40600

Browse files
committed
Support u128/i128 c-variadic arguments
on some platforms
1 parent 93637f3 commit 1b40600

6 files changed

Lines changed: 151 additions & 25 deletions

File tree

compiler/rustc_codegen_llvm/src/intrinsic.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use std::ffi::c_uint;
33
use std::{assert_matches, iter, ptr};
44

55
use rustc_abi::{
6-
Align, BackendRepr, Float, HasDataLayout, Integer, NumScalableVectors, Primitive, Size,
7-
WrappingRange,
6+
Align, BackendRepr, Float, HasDataLayout, NumScalableVectors, Primitive, Size, WrappingRange,
87
};
98
use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh, wants_wasm_eh};
109
use rustc_codegen_ssa::common::{IntPredicate, TypeKind};
@@ -298,11 +297,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
298297
Primitive::Pointer(_) => {
299298
// Pointers are always OK.
300299
}
301-
Primitive::Int(Integer::I128, _) => {
302-
// FIXME: maybe we should support these? At least on 32-bit powerpc
303-
// the logic in LLVM does not handle i128 correctly though.
304-
bug!("the va_arg intrinsic does not support `i128`/`u128`")
305-
}
306300
Primitive::Int(..) => {
307301
let int_width = self.cx().size_of(result.layout.ty).bits();
308302
let target_c_int_width = self.cx().sess().target.options.c_int_width;

compiler/rustc_codegen_llvm/src/va_arg.rs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_abi::{Align, BackendRepr, Endian, HasDataLayout, Primitive, Size};
1+
use rustc_abi::{Align, BackendRepr, Endian, Float, HasDataLayout, Integer, Primitive, Size};
22
use rustc_codegen_ssa::MemFlags;
33
use rustc_codegen_ssa::common::IntPredicate;
44
use rustc_codegen_ssa::mir::operand::OperandRef;
@@ -8,7 +8,7 @@ use rustc_codegen_ssa::traits::{
88
use rustc_middle::bug;
99
use rustc_middle::ty::Ty;
1010
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout};
11-
use rustc_target::spec::{Arch, Env, LlvmAbi, RustcAbi};
11+
use rustc_target::spec::{Arch, Env, HasTargetSpec, LlvmAbi, RustcAbi};
1212

1313
use crate::builder::Builder;
1414
use crate::llvm::{Type, Value};
@@ -30,11 +30,9 @@ fn round_pointer_up_to_alignment<'ll>(
3030
ptr_ty: &'ll Type,
3131
) -> &'ll Value {
3232
let ptr = bx.inbounds_ptradd(addr, bx.const_i32(align.bytes() as i32 - 1));
33-
bx.call_intrinsic(
34-
"llvm.ptrmask",
35-
&[ptr_ty, bx.type_i32()],
36-
&[ptr, bx.const_int(bx.isize_ty, -(align.bytes() as isize) as i64)],
37-
)
33+
let pointer_width = bx.target_spec().pointer_width;
34+
let mask = align.bytes().wrapping_neg() & (u64::MAX >> (64 - pointer_width));
35+
bx.call_intrinsic("llvm.ptrmask", &[ptr_ty, bx.type_isize()], &[ptr, bx.const_usize(mask)])
3836
}
3937

4038
fn emit_direct_ptr_va_arg<'ll, 'tcx>(
@@ -75,6 +73,35 @@ fn emit_direct_ptr_va_arg<'ll, 'tcx>(
7573
}
7674
}
7775

76+
/// Some backends apply special alignment rules to c-variadic arguments.
77+
fn get_param_type_alignment<'ll, 'tcx>(
78+
bx: &mut Builder<'_, 'll, 'tcx>,
79+
layout: TyAndLayout<'tcx>,
80+
) -> Align {
81+
let BackendRepr::Scalar(scalar) = layout.backend_repr else {
82+
bug!("unexpected backend repr {:?}", layout.backend_repr);
83+
};
84+
85+
match bx.cx.tcx.sess.target.arch {
86+
Arch::PowerPC64 => match scalar.primitive() {
87+
Primitive::Int(integer, _) => match integer {
88+
Integer::I8 | Integer::I16 => unreachable!(),
89+
Integer::I32 | Integer::I64 => { /* fall through */ }
90+
Integer::I128 => return Align::EIGHT,
91+
},
92+
Primitive::Float(float) => match float {
93+
Float::F16 | Float::F32 => unreachable!(),
94+
Float::F64 => { /* fall through */ }
95+
Float::F128 => return Align::from_bytes(16).unwrap(),
96+
},
97+
Primitive::Pointer(_) => { /* fall through */ }
98+
},
99+
_ => { /* fall through */ }
100+
}
101+
102+
layout.align.abi
103+
}
104+
78105
enum PassMode {
79106
Direct,
80107
Indirect,
@@ -115,23 +142,23 @@ fn emit_ptr_va_arg<'ll, 'tcx>(
115142
(
116143
bx.cx.layout_of(Ty::new_imm_ptr(bx.cx.tcx, target_ty)).llvm_type(bx.cx),
117144
bx.cx.data_layout().pointer_size(),
118-
bx.cx.data_layout().pointer_align(),
145+
bx.cx.data_layout().pointer_align().abi,
119146
)
120147
} else {
121-
(layout.llvm_type(bx.cx), layout.size, layout.align)
148+
(layout.llvm_type(bx.cx), layout.size, get_param_type_alignment(bx, layout))
122149
};
123150
let (addr, addr_align) = emit_direct_ptr_va_arg(
124151
bx,
125152
list,
126153
size,
127-
align.abi,
154+
align,
128155
slot_size,
129156
allow_higher_align,
130157
force_right_adjust,
131158
);
132159
if indirect {
133160
let tmp_ret = bx.load(llty, addr, addr_align);
134-
bx.load(layout.llvm_type(bx.cx), tmp_ret, align.abi)
161+
bx.load(layout.llvm_type(bx.cx), tmp_ret, align)
135162
} else {
136163
bx.load(llty, addr, addr_align)
137164
}
@@ -765,9 +792,16 @@ fn x86_64_sysv64_va_arg_from_memory<'ll, 'tcx>(
765792
// byte boundary if alignment needed by type exceeds 8 byte boundary.
766793
// It isn't stated explicitly in the standard, but in practice we use
767794
// alignment greater than 16 where necessary.
768-
if layout.layout.align.bytes() > 8 {
769-
unreachable!("all instances of VaArgSafe have an alignment <= 8");
770-
}
795+
let overflow_arg_area_v = if layout.layout.align.bytes() > 8 {
796+
round_pointer_up_to_alignment(
797+
bx,
798+
overflow_arg_area_v,
799+
layout.layout.align.abi,
800+
bx.type_ptr(),
801+
)
802+
} else {
803+
overflow_arg_area_v
804+
};
771805

772806
// AMD64-ABI 3.5.7p5: Step 8. Fetch type from l->overflow_arg_area.
773807
let mem_addr = overflow_arg_area_v;
@@ -1075,6 +1109,7 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
10751109
PassMode::Direct,
10761110
SlotSize::Bytes8,
10771111
AllowHigherAlign::Yes,
1112+
// ForceRightAdjust only takes effect on big-endian architectures.
10781113
ForceRightAdjust::Yes,
10791114
),
10801115
Arch::RiscV32 if target.llvm_abiname == LlvmAbi::Ilp32e => {

library/core/src/ffi/va_list.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,13 @@ mod sealed {
269269
impl Sealed for i16 {}
270270
impl Sealed for i32 {}
271271
impl Sealed for i64 {}
272+
impl Sealed for i128 {}
272273
impl Sealed for isize {}
273274

274275
impl Sealed for u16 {}
275276
impl Sealed for u32 {}
276277
impl Sealed for u64 {}
278+
impl Sealed for u128 {}
277279
impl Sealed for usize {}
278280

279281
impl Sealed for f32 {}
@@ -287,15 +289,27 @@ mod sealed {
287289
///
288290
/// # Safety
289291
///
290-
/// The standard library implements this trait for primitive types that are
291-
/// expected to have a variable argument application-binary interface (ABI) on all
292-
/// platforms.
292+
/// The standard library implements this trait for primitive types that have a variable
293+
/// argument application-binary interface (ABI) on the current platform. The trait is always
294+
/// implemented for:
295+
///
296+
/// - [`c_int`], [`c_long`] and [`c_longlong`]
297+
/// - [`c_uint`], [`c_ulong`] and [`c_ulonglong`]
298+
/// - [`c_double`]
293299
///
294300
/// When C passes variable arguments, integers smaller than [`c_int`] and floats smaller
295301
/// than [`c_double`] are implicitly promoted to [`c_int`] and [`c_double`] respectively.
296302
/// Implementing this trait for types that are subject to this promotion rule is invalid.
297303
///
304+
/// This trait is only implemented for 128-bit integers when the platform defines the `__int128`
305+
/// type.
306+
///
298307
/// [`c_int`]: core::ffi::c_int
308+
/// [`c_long`]: core::ffi::c_long
309+
/// [`c_longlong`]: core::ffi::c_longlong
310+
/// [`c_uint`]: core::ffi::c_uint
311+
/// [`c_ulong`]: core::ffi::c_ulong
312+
/// [`c_ulonglong`]: core::ffi::c_ulonglong
299313
/// [`c_double`]: core::ffi::c_double
300314
// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
301315
// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
@@ -340,6 +354,34 @@ unsafe impl VaArgSafe for u32 {}
340354
unsafe impl VaArgSafe for u64 {}
341355
unsafe impl VaArgSafe for usize {}
342356

357+
// NOTE: this is conservative, we can add more targets later if we're sure 128-bit integers are FFI
358+
// safe there. This is a subset of targets that define __int128.
359+
crate::cfg_select! {
360+
target_pointer_width = "32" => {
361+
// GCC says <https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/_005f_005fint128.html>
362+
//
363+
// > There is no support in GCC for expressing an integer constant of type __int128 for targets
364+
// > with long long integer less than 128 bits wide.
365+
}
366+
target_env = "msvc" => {
367+
// Per https://learn.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-170, __int128 is
368+
// not recognized by msvc.
369+
}
370+
any(
371+
target_arch = "x86_64",
372+
target_arch = "aarch64",
373+
target_arch = "riscv64",
374+
target_arch = "loongarch64",
375+
target_arch = "s390x",
376+
target_arch = "powerpc64"
377+
) => {
378+
unsafe impl VaArgSafe for i128 {}
379+
unsafe impl VaArgSafe for u128 {}
380+
}
381+
_ => {
382+
}
383+
}
384+
343385
unsafe impl VaArgSafe for f64 {}
344386

345387
unsafe impl<T> VaArgSafe for *mut T {}
@@ -352,9 +394,11 @@ const _: () = {
352394
va_arg_safe_check::<crate::ffi::c_int>();
353395
va_arg_safe_check::<crate::ffi::c_uint>();
354396
va_arg_safe_check::<crate::ffi::c_long>();
397+
355398
va_arg_safe_check::<crate::ffi::c_ulong>();
356399
va_arg_safe_check::<crate::ffi::c_longlong>();
357400
va_arg_safe_check::<crate::ffi::c_ulonglong>();
401+
358402
va_arg_safe_check::<crate::ffi::c_double>();
359403
};
360404

tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@ pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize {
5757
if compare_c_str(ap.arg::<*const c_char>(), c"Correct") { 0 } else { 0xff }
5858
}
5959

60+
#[unsafe(no_mangle)]
61+
pub unsafe extern "C" fn check_list_i128(mut ap: VaList) -> usize {
62+
cfg_select! {
63+
target_pointer_width = "32" => {}
64+
target_env = "msvc" => {}
65+
any(
66+
target_arch = "x86_64",
67+
target_arch = "aarch64",
68+
target_arch = "riscv64",
69+
target_arch = "loongarch64",
70+
target_arch = "s390x",
71+
target_arch = "powerpc64"
72+
) => {
73+
continue_if!(ap.arg::<i128>() == -42);
74+
// use a 32-bit value here to test the alignment logic.
75+
continue_if!(ap.arg::<c_int>() == 0xAAAA_AAAAu32.cast_signed());
76+
continue_if!(ap.arg::<u128>() == u128::MAX);
77+
return 0;
78+
}
79+
_ => {}
80+
}
81+
82+
// We're running this test on a platform where rustc does not implement
83+
// VaArgSafe for i128. The implementation should be added to std if this
84+
// comes up.
85+
0xFF
86+
}
87+
6088
#[unsafe(no_mangle)]
6189
pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize {
6290
continue_if!(ap.arg::<c_int>() == 42);

tests/run-make/c-link-to-rust-va-list-fn/test.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern size_t check_list_0(va_list ap);
88
extern size_t check_list_1(va_list ap);
99
extern size_t check_list_2(va_list ap);
1010
extern size_t check_list_copy_0(va_list ap);
11+
extern size_t check_list_i128(va_list ap);
1112
extern size_t check_varargs_0(int fixed, ...);
1213
extern size_t check_varargs_1(int fixed, ...);
1314
extern size_t check_varargs_2(int fixed, ...);
@@ -38,6 +39,10 @@ int main(int argc, char* argv[]) {
3839

3940
assert(test_rust(check_list_copy_0, 6.28, 16, 'A', "Skip Me!", "Correct") == 0);
4041

42+
#if defined(__SIZEOF_INT128__)
43+
assert(test_rust(check_list_i128, (__int128)-42, 0xAAAAAAAA, (unsigned __int128)-1) == 0);
44+
#endif
45+
4146
assert(check_varargs_0(0, 42, "Hello, World!") == 0);
4247

4348
assert(check_varargs_1(0, 3.14, 12l, 'A', 0x1LL) == 0);

tests/ui/c-variadic/roundtrip.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use std::ffi::*;
1212
const unsafe extern "C" fn variadic<T: VaArgSafe>(mut ap: ...) -> (T, T) {
1313
let x = ap.arg::<T>();
1414
// Intersperse a small type to test alignment logic. A `u32` (i.e. `c_uint`) is the smallest
15-
// type that implements `VaArgSafe`: smaller types would automatically be promoted.
15+
// type that implements `VaArgSafe` (except on some 16-bit targets): smaller types would
16+
// automatically be promoted.
1617
assert!(ap.arg::<u32>() == 0xAAAA_AAAA);
1718
let y = ap.arg::<T>();
1819

@@ -74,5 +75,24 @@ fn main() {
7475
static mut B: u32 = 2u32;
7576
roundtrip_ptr!(*const u32, &raw const A, &raw const B);
7677
roundtrip_ptr!(*mut u32, &raw mut A, &raw mut B);
78+
79+
// The 128-bit integers only implement VaArgSafe on some targets, a subset of those that
80+
// define `__int128`. We test some of those targets here.
81+
cfg_select! {
82+
target_pointer_width = "32" => {}
83+
target_env = "msvc" => {}
84+
any(
85+
target_arch = "x86_64",
86+
target_arch = "aarch64",
87+
target_arch = "riscv64",
88+
target_arch = "loongarch64",
89+
target_arch = "s390x",
90+
target_arch = "powerpc64"
91+
) => {
92+
roundtrip!(i128, -1, -2);
93+
roundtrip!(u128, 1, 2);
94+
}
95+
_ => {}
96+
}
7797
}
7898
}

0 commit comments

Comments
 (0)