Skip to content

Commit 8afe72a

Browse files
committed
Support u128/i128 c-variadic arguments
On platforms where `clang` defines `__int128`.
1 parent beae781 commit 8afe72a

7 files changed

Lines changed: 217 additions & 35 deletions

File tree

compiler/rustc_codegen_llvm/src/intrinsic.rs

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

55
use rustc_abi::{
6-
AddressSpace, Align, BackendRepr, CVariadicStatus, Float, HasDataLayout, Integer,
7-
NumScalableVectors, Primitive, Size, WrappingRange,
6+
AddressSpace, Align, BackendRepr, CVariadicStatus, Float, HasDataLayout, NumScalableVectors,
7+
Primitive, Size, WrappingRange,
88
};
99
use rustc_codegen_ssa::RetagInfo;
1010
use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh, wants_wasm_eh};
@@ -318,11 +318,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
318318
Primitive::Pointer(_) => {
319319
// Pointers are always OK.
320320
}
321-
Primitive::Int(Integer::I128, _) => {
322-
// FIXME: maybe we should support these? At least on 32-bit powerpc
323-
// the logic in LLVM does not handle i128 correctly though.
324-
bug!("the va_arg intrinsic does not support `i128`/`u128`")
325-
}
326321
Primitive::Int(..) => {
327322
let int_width = self.cx().size_of(result_layout.ty).bits();
328323
let target_c_int_width = self.cx().sess().target.options.c_int_width;

compiler/rustc_codegen_llvm/src/va_arg.rs

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use rustc_abi::{Align, BackendRepr, CVariadicStatus, Endian, HasDataLayout, Primitive, Size};
2-
use rustc_codegen_ssa::MemFlags;
1+
use rustc_abi::{
2+
Align, BackendRepr, CVariadicStatus, Endian, Float, HasDataLayout, Integer, Primitive, Size,
3+
};
34
use rustc_codegen_ssa::common::IntPredicate;
45
use rustc_codegen_ssa::mir::operand::OperandRef;
56
use rustc_codegen_ssa::traits::{
@@ -77,6 +78,35 @@ fn emit_direct_ptr_va_arg<'ll, 'tcx>(
7778
}
7879
}
7980

81+
/// Some backends apply special alignment rules to c-variadic arguments.
82+
fn get_param_type_alignment<'ll, 'tcx>(
83+
bx: &mut Builder<'_, 'll, 'tcx>,
84+
layout: TyAndLayout<'tcx>,
85+
) -> Align {
86+
let BackendRepr::Scalar(scalar) = layout.backend_repr else {
87+
bug!("unexpected backend repr {:?}", layout.backend_repr);
88+
};
89+
90+
match bx.cx.tcx.sess.target.arch {
91+
Arch::PowerPC64 => match scalar.primitive() {
92+
Primitive::Int(integer, _) => match integer {
93+
Integer::I8 | Integer::I16 => unreachable!(),
94+
Integer::I32 | Integer::I64 => { /* fall through */ }
95+
Integer::I128 => return Align::EIGHT,
96+
},
97+
Primitive::Float(float) => match float {
98+
Float::F16 | Float::F32 => unreachable!(),
99+
Float::F64 => { /* fall through */ }
100+
Float::F128 => return Align::from_bytes(16).unwrap(),
101+
},
102+
Primitive::Pointer(_) => { /* fall through */ }
103+
},
104+
_ => { /* fall through */ }
105+
}
106+
107+
layout.align.abi
108+
}
109+
80110
enum PassMode {
81111
Direct,
82112
Indirect,
@@ -136,23 +166,23 @@ fn emit_ptr_va_arg<'ll, 'tcx>(
136166
(
137167
bx.cx.layout_of(Ty::new_imm_ptr(bx.cx.tcx, target_ty)).llvm_type(bx.cx),
138168
bx.cx.data_layout().pointer_size(),
139-
bx.cx.data_layout().pointer_align(),
169+
bx.cx.data_layout().pointer_align().abi,
140170
)
141171
} else {
142-
(layout.llvm_type(bx.cx), layout.size, layout.align)
172+
(layout.llvm_type(bx.cx), layout.size, get_param_type_alignment(bx, layout))
143173
};
144174
let (addr, addr_align) = emit_direct_ptr_va_arg(
145175
bx,
146176
list,
147177
size,
148-
align.abi,
178+
align,
149179
slot_size,
150180
allow_higher_align,
151181
force_right_adjust,
152182
);
153183
if indirect {
154184
let tmp_ret = bx.load(llty, addr, addr_align);
155-
bx.load(layout.llvm_type(bx.cx), tmp_ret, align.abi)
185+
bx.load(layout.llvm_type(bx.cx), tmp_ret, align)
156186
} else {
157187
bx.load(llty, addr, addr_align)
158188
}
@@ -585,8 +615,10 @@ fn emit_x86_64_sysv64_va_arg<'ll, 'tcx>(
585615
// registers. In the case: l->gp_offset > 48 - num_gp * 8 or
586616
// l->fp_offset > 176 - num_fp * 16 go to step 7.
587617

618+
// We support x86_64-unknown-linux-gnux32 which uses 4-byte pointers.
588619
let unsigned_int_offset = 4;
589-
let ptr_offset = 8;
620+
let ptr_offset = bx.tcx().data_layout.pointer_size().bytes();
621+
590622
let gp_offset_ptr = va_list_addr;
591623
let fp_offset_ptr = bx.inbounds_ptradd(va_list_addr, bx.cx.const_usize(unsigned_int_offset));
592624

@@ -660,7 +692,7 @@ fn emit_x86_64_sysv64_va_arg<'ll, 'tcx>(
660692
let reg_hi_addr = bx.inbounds_ptradd(reg_lo_addr, bx.const_i32(16));
661693

662694
let align = layout.layout.align().abi;
663-
let tmp = bx.alloca(layout.layout.size(), align);
695+
let tmp = bx.alloca(layout.size, layout.align.abi);
664696

665697
let reg_lo = bx.load(ty_lo, reg_lo_addr, align_lo);
666698
let reg_hi = bx.load(ty_hi, reg_hi_addr, align_hi);
@@ -683,7 +715,7 @@ fn emit_x86_64_sysv64_va_arg<'ll, 'tcx>(
683715
Primitive::Int(_, _) | Primitive::Pointer(_) => (gp_addr, fp_addr),
684716
};
685717

686-
let tmp = bx.alloca(layout.layout.size(), layout.layout.align().abi);
718+
let tmp = bx.alloca(layout.size, layout.align.abi);
687719

688720
let reg_lo = bx.load(ty_lo, reg_lo_addr, align_lo);
689721
let reg_hi = bx.load(ty_hi, reg_hi_addr, align_hi);
@@ -751,16 +783,12 @@ fn copy_to_temporary_if_more_aligned<'ll, 'tcx>(
751783
src_align: Align,
752784
) -> &'ll Value {
753785
if layout.layout.align.abi > src_align {
754-
let tmp = bx.alloca(layout.layout.size(), layout.layout.align().abi);
755-
bx.memcpy(
756-
tmp,
757-
layout.layout.align.abi,
758-
reg_addr,
759-
src_align,
760-
bx.const_u32(layout.layout.size().bytes() as u32),
761-
MemFlags::empty(),
762-
None,
763-
);
786+
assert!(layout.ty.is_integral());
787+
788+
// A memcpy below optimizes poorly for 128-bit integers.
789+
let tmp = bx.alloca(layout.size, layout.align.abi);
790+
let val = bx.load(layout.llvm_type(bx), reg_addr, src_align);
791+
bx.store(val, tmp, layout.align.abi);
764792
tmp
765793
} else {
766794
reg_addr
@@ -782,9 +810,11 @@ fn x86_64_sysv64_va_arg_from_memory<'ll, 'tcx>(
782810
// byte boundary if alignment needed by type exceeds 8 byte boundary.
783811
// It isn't stated explicitly in the standard, but in practice we use
784812
// alignment greater than 16 where necessary.
785-
if layout.layout.align.bytes() > 8 {
786-
unreachable!("all instances of VaArgSafe have an alignment <= 8");
787-
}
813+
let overflow_arg_area_v = if layout.layout.align.bytes() > 8 {
814+
round_pointer_up_to_alignment(bx, overflow_arg_area_v, layout.layout.align.abi)
815+
} else {
816+
overflow_arg_area_v
817+
};
788818

789819
// AMD64-ABI 3.5.7p5: Step 8. Fetch type from l->overflow_arg_area.
790820
let mem_addr = overflow_arg_area_v;
@@ -1071,7 +1101,8 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
10711101
Arch::AArch64 => emit_aapcs_va_arg(bx, addr, target_ty),
10721102
Arch::Arm => {
10731103
// Types wider than 16 bytes are not currently supported. Clang has special logic for
1074-
// such types, but `VaArgSafe` is not implemented for any type that is this large.
1104+
// such types, but `VaArgSafe` is not implemented for any type that is this large on
1105+
// arm (i.e. 32-bit) targets.
10751106
assert!(bx.cx.size_of(target_ty).bytes() <= 16);
10761107

10771108
emit_ptr_va_arg(
@@ -1093,6 +1124,7 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
10931124
PassMode::Direct,
10941125
SlotSize::Bytes8,
10951126
AllowHigherAlign::Yes,
1127+
// ForceRightAdjust only takes effect on big-endian architectures.
10961128
ForceRightAdjust::Yes,
10971129
),
10981130
Arch::RiscV32 if target.llvm_abiname == LlvmAbi::Ilp32e => {

library/core/src/ffi/va_list.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ const impl<'f> Drop for VaList<'f> {
299299
/// and [`c_float`] is promoted to [`c_double`]. Implementing this trait for types that are
300300
/// subject to this promotion rule is invalid.
301301
///
302+
/// This trait is only implemented for 128-bit integers when the platform defines the `__int128`
303+
/// type.
304+
///
302305
/// [`c_int`]: core::ffi::c_int
303306
/// [`c_long`]: core::ffi::c_long
304307
/// [`c_longlong`]: core::ffi::c_longlong
@@ -310,8 +313,8 @@ const impl<'f> Drop for VaList<'f> {
310313
/// [`c_float`]: core::ffi::c_float
311314
/// [`c_double`]: core::ffi::c_double
312315
// We may unseal this trait in the future, but currently our `va_arg` implementations don't support
313-
// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
314-
// to accept unsupported types in the meantime.
316+
// types with a non-scalar layout. Inline assembly can be used to accept unsupported types in the
317+
// meantime.
315318
#[lang = "va_arg_safe"]
316319
pub impl(self) unsafe trait VaArgSafe: Copy {}
317320

@@ -352,6 +355,51 @@ unsafe impl VaArgSafe for u32 {}
352355
unsafe impl VaArgSafe for u64 {}
353356
unsafe impl VaArgSafe for usize {}
354357

358+
// Implement `VaArgSafe` for 128-bit integers on targets where clang provides `__int128`.
359+
//
360+
// GCC does not implement `__int128` for any 16-bit/32-bit target:
361+
//
362+
// https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/_005f_005fint128.html
363+
//
364+
// > There is no support in GCC for expressing an integer constant of type __int128 for targets
365+
// > with long long integer less than 128 bits wide.
366+
//
367+
// Per https://learn.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-170, MSVC does not
368+
// define `__int128`.
369+
//
370+
// Clang is slightly more permissive: it defines `__int128` on wasm32 (a 32-bit target) and also
371+
// does provide `__int128` on 64-bit `*-pc-windows-msvc`, and we follow suit.
372+
cfg_select! {
373+
any(
374+
target_arch = "aarch64",
375+
target_arch = "amdgpu",
376+
target_arch = "arm64ec",
377+
target_arch = "bpf",
378+
target_arch = "loongarch64",
379+
target_arch = "mips64",
380+
target_arch = "mips64r6",
381+
target_arch = "nvptx64",
382+
target_arch = "powerpc64",
383+
target_arch = "riscv64",
384+
target_arch = "s390x",
385+
target_arch = "sparc64",
386+
target_arch = "wasm32",
387+
target_arch = "wasm64",
388+
target_arch = "x86_64",
389+
) => {
390+
#[cfg(not(any(target_arch = "wasm32", target_abi = "x32", target_pointer_width = "64")))]
391+
compile_error!("unexpected target architecture for 128-bit c-variadic");
392+
393+
#[unstable_feature_bound(c_variadic_int128)]
394+
#[unstable(feature = "c_variadic_int128", issue = "155752")]
395+
unsafe impl VaArgSafe for i128 {}
396+
#[unstable_feature_bound(c_variadic_int128)]
397+
#[unstable(feature = "c_variadic_int128", issue = "155752")]
398+
unsafe impl VaArgSafe for u128 {}
399+
}
400+
_ => {}
401+
}
402+
355403
unsafe impl VaArgSafe for f64 {}
356404

357405
unsafe impl<T> VaArgSafe for *mut T {}

tests/assembly-llvm/c-variadic/sparc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub unsafe trait VaArgSafe {}
2121

2222
unsafe impl VaArgSafe for i32 {}
2323
unsafe impl VaArgSafe for i64 {}
24+
#[cfg(target_pointer_width = "64")]
25+
unsafe impl VaArgSafe for i128 {}
2426
unsafe impl VaArgSafe for f64 {}
2527
unsafe impl<T> VaArgSafe for *const T {}
2628

@@ -105,6 +107,22 @@ unsafe extern "C" fn read_i64(ap: &mut VaList<'_>) -> i64 {
105107
va_arg(ap)
106108
}
107109

110+
#[unsafe(no_mangle)]
111+
#[cfg(target_pointer_width = "64")]
112+
unsafe extern "C" fn read_i128(ap: &mut VaList<'_>) -> i128 {
113+
// SPARC64-LABEL: read_i128
114+
// SPARC64: ldx [%o0], %o1
115+
// SPARC64-NEXT: add %o1, 15, %o1
116+
// SPARC64-NEXT: and %o1, -16, %o1
117+
// SPARC64-NEXT: add %o1, 16, %o2
118+
// SPARC64-NEXT: stx %o2, [%o0]
119+
// SPARC64-NEXT: ldx [%o1], %o0
120+
// SPARC64-NEXT: or %o1, 8, %o1
121+
// SPARC64-NEXT: retl
122+
// SPARC64-NEXT: ldx [%o1], %o1
123+
va_arg(ap)
124+
}
125+
108126
#[unsafe(no_mangle)]
109127
unsafe extern "C" fn read_ptr(ap: &mut VaList<'_>) -> *const u8 {
110128
// SPARC: read_ptr = read_i32

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![crate_type = "staticlib"]
2-
#![feature(c_variadic)]
2+
#![feature(c_variadic, c_variadic_int128)]
33

44
use core::ffi::{CStr, VaList, c_char, c_double, c_int, c_long, c_longlong};
55

@@ -57,6 +57,49 @@ pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize {
5757
if compare_c_str(ap.next_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+
any(
64+
target_arch = "aarch64",
65+
target_arch = "amdgpu",
66+
target_arch = "arm64ec",
67+
target_arch = "bpf",
68+
target_arch = "loongarch64",
69+
target_arch = "mips64",
70+
target_arch = "mips64r6",
71+
target_arch = "nvptx64",
72+
target_arch = "powerpc64",
73+
target_arch = "riscv64",
74+
target_arch = "s390x",
75+
target_arch = "sparc64",
76+
target_arch = "wasm32",
77+
target_arch = "wasm64",
78+
target_arch = "x86_64",
79+
) => {
80+
#[cfg(not(any(
81+
target_arch = "wasm32",
82+
target_abi = "x32",
83+
target_pointer_width = "64",
84+
)))]
85+
compile_error!("unexpected target architecture for 128-bit c-variadic");
86+
87+
continue_if!(ap.next_arg::<i128>() == -42);
88+
// use a 32-bit value here to test the alignment logic.
89+
continue_if!(ap.next_arg::<c_int>() == 0xAAAA_AAAAu32.cast_signed());
90+
continue_if!(ap.next_arg::<u128>() == u128::MAX);
91+
92+
return 0;
93+
}
94+
_ => {
95+
// This function was called a platform where rustc does not implement
96+
// VaArgSafe for i128 but clang does define __int128. Rustc should add
97+
// the implementation if this comes up.
98+
0xFF
99+
}
100+
}
101+
}
102+
60103
#[unsafe(no_mangle)]
61104
pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize {
62105
continue_if!(ap.next_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);

0 commit comments

Comments
 (0)