Skip to content

Commit cc3a394

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

7 files changed

Lines changed: 214 additions & 22 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: 46 additions & 12 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;
@@ -77,6 +77,35 @@ fn emit_direct_ptr_va_arg<'ll, 'tcx>(
7777
}
7878
}
7979

80+
/// Some backends apply special alignment rules to c-variadic arguments.
81+
fn get_param_type_alignment<'ll, 'tcx>(
82+
bx: &mut Builder<'_, 'll, 'tcx>,
83+
layout: TyAndLayout<'tcx>,
84+
) -> Align {
85+
let BackendRepr::Scalar(scalar) = layout.backend_repr else {
86+
bug!("unexpected backend repr {:?}", layout.backend_repr);
87+
};
88+
89+
match bx.cx.tcx.sess.target.arch {
90+
Arch::PowerPC64 => match scalar.primitive() {
91+
Primitive::Int(integer, _) => match integer {
92+
Integer::I8 | Integer::I16 => unreachable!(),
93+
Integer::I32 | Integer::I64 => { /* fall through */ }
94+
Integer::I128 => return Align::EIGHT,
95+
},
96+
Primitive::Float(float) => match float {
97+
Float::F16 | Float::F32 => unreachable!(),
98+
Float::F64 => { /* fall through */ }
99+
Float::F128 => return Align::from_bytes(16).unwrap(),
100+
},
101+
Primitive::Pointer(_) => { /* fall through */ }
102+
},
103+
_ => { /* fall through */ }
104+
}
105+
106+
layout.align.abi
107+
}
108+
80109
enum PassMode {
81110
Direct,
82111
Indirect,
@@ -136,23 +165,23 @@ fn emit_ptr_va_arg<'ll, 'tcx>(
136165
(
137166
bx.cx.layout_of(Ty::new_imm_ptr(bx.cx.tcx, target_ty)).llvm_type(bx.cx),
138167
bx.cx.data_layout().pointer_size(),
139-
bx.cx.data_layout().pointer_align(),
168+
bx.cx.data_layout().pointer_align().abi,
140169
)
141170
} else {
142-
(layout.llvm_type(bx.cx), layout.size, layout.align)
171+
(layout.llvm_type(bx.cx), layout.size, get_param_type_alignment(bx, layout))
143172
};
144173
let (addr, addr_align) = emit_direct_ptr_va_arg(
145174
bx,
146175
list,
147176
size,
148-
align.abi,
177+
align,
149178
slot_size,
150179
allow_higher_align,
151180
force_right_adjust,
152181
);
153182
if indirect {
154183
let tmp_ret = bx.load(llty, addr, addr_align);
155-
bx.load(layout.llvm_type(bx.cx), tmp_ret, align.abi)
184+
bx.load(layout.llvm_type(bx.cx), tmp_ret, align)
156185
} else {
157186
bx.load(llty, addr, addr_align)
158187
}
@@ -585,8 +614,10 @@ fn emit_x86_64_sysv64_va_arg<'ll, 'tcx>(
585614
// registers. In the case: l->gp_offset > 48 - num_gp * 8 or
586615
// l->fp_offset > 176 - num_fp * 16 go to step 7.
587616

617+
// We support x86_64-unknown-linux-gnux32 which uses 4-byte pointers.
588618
let unsigned_int_offset = 4;
589-
let ptr_offset = 8;
619+
let ptr_offset = bx.tcx().data_layout.pointer_size().bytes();
620+
590621
let gp_offset_ptr = va_list_addr;
591622
let fp_offset_ptr = bx.inbounds_ptradd(va_list_addr, bx.cx.const_usize(unsigned_int_offset));
592623

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

662693
let align = layout.layout.align().abi;
663-
let tmp = bx.alloca(layout.layout.size(), align);
694+
let tmp = bx.alloca_with_ty(layout);
664695

665696
let reg_lo = bx.load(ty_lo, reg_lo_addr, align_lo);
666697
let reg_hi = bx.load(ty_hi, reg_hi_addr, align_hi);
@@ -683,7 +714,7 @@ fn emit_x86_64_sysv64_va_arg<'ll, 'tcx>(
683714
Primitive::Int(_, _) | Primitive::Pointer(_) => (gp_addr, fp_addr),
684715
};
685716

686-
let tmp = bx.alloca(layout.layout.size(), layout.layout.align().abi);
717+
let tmp = bx.alloca_with_ty(layout);
687718

688719
let reg_lo = bx.load(ty_lo, reg_lo_addr, align_lo);
689720
let reg_hi = bx.load(ty_hi, reg_hi_addr, align_hi);
@@ -751,7 +782,7 @@ fn copy_to_temporary_if_more_aligned<'ll, 'tcx>(
751782
src_align: Align,
752783
) -> &'ll Value {
753784
if layout.layout.align.abi > src_align {
754-
let tmp = bx.alloca(layout.layout.size(), layout.layout.align().abi);
785+
let tmp = bx.alloca_with_ty(layout);
755786
bx.memcpy(
756787
tmp,
757788
layout.layout.align.abi,
@@ -782,9 +813,11 @@ fn x86_64_sysv64_va_arg_from_memory<'ll, 'tcx>(
782813
// byte boundary if alignment needed by type exceeds 8 byte boundary.
783814
// It isn't stated explicitly in the standard, but in practice we use
784815
// alignment greater than 16 where necessary.
785-
if layout.layout.align.bytes() > 8 {
786-
unreachable!("all instances of VaArgSafe have an alignment <= 8");
787-
}
816+
let overflow_arg_area_v = if layout.layout.align.bytes() > 8 {
817+
round_pointer_up_to_alignment(bx, overflow_arg_area_v, layout.layout.align.abi)
818+
} else {
819+
overflow_arg_area_v
820+
};
788821

789822
// AMD64-ABI 3.5.7p5: Step 8. Fetch type from l->overflow_arg_area.
790823
let mem_addr = overflow_arg_area_v;
@@ -1091,6 +1124,7 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
10911124
PassMode::Direct,
10921125
SlotSize::Bytes8,
10931126
AllowHigherAlign::Yes,
1127+
// ForceRightAdjust only takes effect on big-endian architectures.
10941128
ForceRightAdjust::Yes,
10951129
),
10961130
Arch::RiscV32 if target.llvm_abiname == LlvmAbi::Ilp32e => {

library/core/src/ffi/va_list.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,15 @@ mod sealed {
280280
impl Sealed for f32 {}
281281
impl Sealed for f64 {}
282282

283+
// The unstable annotation here is not needed, but makes sure that the feature gate is defined
284+
// on all targets, even though the actual instances of VaArgSafe for i128/u128 are not.
285+
#[unstable_feature_bound(c_variadic_int128)]
286+
#[unstable(feature = "c_variadic_int128", issue = "155752")]
287+
impl Sealed for i128 {}
288+
#[unstable_feature_bound(c_variadic_int128)]
289+
#[unstable(feature = "c_variadic_int128", issue = "155752")]
290+
impl Sealed for u128 {}
291+
283292
impl<T> Sealed for *mut T {}
284293
impl<T> Sealed for *const T {}
285294
}
@@ -304,6 +313,9 @@ mod sealed {
304313
/// and [`c_float`] is promoted to [`c_double`]. Implementing this trait for types that are
305314
/// subject to this promotion rule is invalid.
306315
///
316+
/// This trait is only implemented for 128-bit integers when the platform defines the `__int128`
317+
/// type.
318+
///
307319
/// [`c_int`]: core::ffi::c_int
308320
/// [`c_long`]: core::ffi::c_long
309321
/// [`c_longlong`]: core::ffi::c_longlong
@@ -357,6 +369,51 @@ unsafe impl VaArgSafe for u32 {}
357369
unsafe impl VaArgSafe for u64 {}
358370
unsafe impl VaArgSafe for usize {}
359371

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

362419
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
@@ -20,6 +20,8 @@ pub unsafe trait VaArgSafe {}
2020

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

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

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