Skip to content

Prototype VaList proposal #141980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions compiler/rustc_abi/src/layout/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug {
fn is_tuple(this: TyAndLayout<'a, Self>) -> bool;
fn is_unit(this: TyAndLayout<'a, Self>) -> bool;
fn is_transparent(this: TyAndLayout<'a, Self>) -> bool;
/// Returns `true` if the type is always passed indirectly. Currently only
/// used for `VaList`s.
fn is_pass_indirectly(this: TyAndLayout<'a, Self>) -> bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only see this as having an effect on x86-64 and aarch64, but doesn't the argument-passing algorithm already enforce this by making sure that VaList gets passed via Memory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is only needed by the code in rustc_target::callconv to ensure that VaList gets PassMode::Indirect { on_stack: false, .. }. repr_options_of_def sets the PASS_INDIRECTLY ReprFlag for VaList when it needs to be passed indirectly for non-rustic ABIs, however there is no way to directly access ReprFlags from the calling convention code. There is already a method is_transparent on TyAbiInterface to expose the IS_TRANSPARENT ReprFlag, so I used a similar pattern to expose PASS_INDIRECTLY to the calling convention code. Without this method, the System V x86-64 ABI would pass VaList with PassMode::Indirect { on_stack: true, .. }.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And just to finish the thought: the difference is important (only) in an FFI context: a foreign extern "C" { fn(ap: VaList); } would expect ap to be passed as PassMode::Indirect { on_stack: false, .. }

Copy link
Member

@workingjubilee workingjubilee Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh, fascinating.

}

impl<'a, Ty> TyAndLayout<'a, Ty> {
Expand Down Expand Up @@ -272,6 +275,13 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
Ty::is_transparent(self)
}

pub fn is_pass_indirectly<C>(self) -> bool
where
Ty: TyAbiInterface<'a, C>,
{
Ty::is_pass_indirectly(self)
}

/// Finds the one field that is not a 1-ZST.
/// Returns `None` if there are multiple non-1-ZST fields or only 1-ZST-fields.
pub fn non_1zst_field<C>(&self, cx: &C) -> Option<(usize, Self)>
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ bitflags! {
// Other flags can still inhibit reordering and thus randomization.
// The seed stored in `ReprOptions.field_shuffle_seed`.
const RANDOMIZE_LAYOUT = 1 << 4;
// If true, the type is always passed indirectly in C-like ABIs.
// Currently only used for `VaList`s.
const PASS_INDIRECTLY = 1 << 5;
// Any of these flags being set prevent field reordering optimisation.
const FIELD_ORDER_UNOPTIMIZABLE = ReprFlags::IS_C.bits()
| ReprFlags::IS_SIMD.bits()
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_ssa/src/traits/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ pub trait IntrinsicCallBuilderMethods<'tcx>: BackendTypes {
vtable_byte_offset: u64,
typeid: Self::Metadata,
) -> Self::Value;
/// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in
/// Trait method used to inject `va_start` on the "spoofed" `VaList` in
/// Rust defined C-variadic functions.
fn va_start(&mut self, val: Self::Value) -> Self::Value;
/// Trait method used to inject `va_end` on the "spoofed" `VaListImpl` before
/// Trait method used to inject `va_end` on the "spoofed" `VaList` before
/// Rust defined C-variadic functions return.
fn va_end(&mut self, val: Self::Value) -> Self::Value;
}
6 changes: 5 additions & 1 deletion compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{cmp, fmt};

use rustc_abi::{
AddressSpace, Align, ExternAbi, FieldIdx, FieldsShape, HasDataLayout, LayoutData, PointeeInfo,
PointerKind, Primitive, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout,
PointerKind, Primitive, ReprFlags, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout,
TyAbiInterface, VariantIdx, Variants,
};
use rustc_error_messages::DiagMessage;
Expand Down Expand Up @@ -1165,6 +1165,10 @@ where
fn is_transparent(this: TyAndLayout<'tcx>) -> bool {
matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().transparent())
}

fn is_pass_indirectly(this: TyAndLayout<'tcx>) -> bool {
matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().flags.contains(ReprFlags::PASS_INDIRECTLY))
}
}

/// Calculates whether a function's ABI can unwind or not.
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,12 @@ impl<'tcx> TyCtxt<'tcx> {
flags.insert(ReprFlags::IS_LINEAR);
}

if self.is_lang_item(did.to_def_id(), LangItem::VaList)
&& !flags.contains(ReprFlags::IS_TRANSPARENT)
{
flags.insert(ReprFlags::PASS_INDIRECTLY);
}

ReprOptions { int: size, align: max_align, pack: min_pack, flags, field_shuffle_seed }
}

Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_target/src/callconv/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ where
// Not touching this...
return;
}
// `is_pass_indirectly` is only `true` for `VaList`, which would be passed indirectly by the
// logic below anyway, so this is just here to make it explicit that this case is handled.
if arg.layout.is_pass_indirectly() {
arg.make_indirect();
return;
}
if !arg.layout.is_aggregate() {
if kind == AbiKind::DarwinPCS {
// On Darwin, when passing an i8/i16, it must be sign-extended to 32 bits,
Expand Down
16 changes: 13 additions & 3 deletions compiler/rustc_target/src/callconv/powerpc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use rustc_abi::TyAbiInterface;

use crate::callconv::{ArgAbi, FnAbi};
use crate::spec::HasTargetSpec;

Expand All @@ -9,7 +11,10 @@ fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {
}
}

fn classify_arg<Ty>(cx: &impl HasTargetSpec, arg: &mut ArgAbi<'_, Ty>) {
fn classify_arg<'a, Ty, C>(cx: &impl HasTargetSpec, arg: &mut ArgAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if arg.is_ignore() {
// powerpc-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs.
if cx.target_spec().os == "linux"
Expand All @@ -20,14 +25,19 @@ fn classify_arg<Ty>(cx: &impl HasTargetSpec, arg: &mut ArgAbi<'_, Ty>) {
}
return;
}
if arg.layout.is_aggregate() {
// `is_pass_indirectly` is only `true` for `VaList` which is already an aggregate, so the
// `.is_pass_indirectly()` call is just to make it explicit that this case is handled.
if arg.layout.is_aggregate() || arg.layout.is_pass_indirectly() {
arg.make_indirect();
} else {
arg.extend_integer_width_to(32);
}
}

pub(crate) fn compute_abi_info<Ty>(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'_, Ty>) {
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if !fn_abi.ret.is_ignore() {
classify_ret(&mut fn_abi.ret);
}
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_target/src/callconv/s390x.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ where
}
return;
}
// `is_pass_indirectly` is only `true` for `VaList`, which would be passed indirectly by the
// logic below anyway, so this is just here to make it explicit that this case is handled.
if arg.layout.is_pass_indirectly() {
arg.make_indirect();
return;
}

let size = arg.layout.size;
if size.bits() <= 128 {
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_target/src/callconv/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ where
// Not touching this...
return;
}
if is_arg && arg.layout.is_pass_indirectly() {
int_regs = int_regs.saturating_sub(1);
arg.make_indirect();
return;
}
let mut cls_or_mem = classify_arg(cx, arg);

if is_arg {
Expand Down
13 changes: 11 additions & 2 deletions compiler/rustc_target/src/callconv/x86_win64.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind, Size};
use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind, Size, TyAbiInterface};

use crate::callconv::{ArgAbi, FnAbi, Reg};
use crate::spec::{HasTargetSpec, RustcAbi};

// Win64 ABI: https://docs.microsoft.com/en-us/cpp/build/parameter-passing

pub(crate) fn compute_abi_info<Ty>(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'_, Ty>) {
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
let fixup = |a: &mut ArgAbi<'_, Ty>, is_ret: bool| {
match a.layout.backend_repr {
BackendRepr::Memory { sized: false } => {}
Expand Down Expand Up @@ -59,6 +62,12 @@ pub(crate) fn compute_abi_info<Ty>(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'
arg.make_indirect_from_ignore();
continue;
}
// The `win64` ABI can be used on non-Windows targets which set `PASS_INDIRECTLY` on
// `VaList`, so that case is handled here.
if arg.layout.is_pass_indirectly() {
arg.make_indirect();
continue;
}
fixup(arg, false);
}
// FIXME: We should likely also do something about ZST return types, similar to above.
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod c_str;
issue = "44930",
reason = "the `c_variadic` feature has not been properly tested on all supported platforms"
)]
pub use self::va_list::{VaArgSafe, VaList, VaListImpl};
pub use self::va_list::{VaArgSafe, VaList};

#[unstable(
feature = "c_variadic",
Expand Down
Loading
Loading