Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
93991f2
add `GlobalAlloc::VaList`
folkertdev Jan 2, 2026
7922c56
allow `const fn` to be c-variadic
folkertdev Jan 2, 2026
a0035e8
make `Va::arg` and `VaList::drop` `const fn`s
folkertdev Jan 2, 2026
c4a6ae9
on call, split the standard and c-variadic arguments
folkertdev Jan 2, 2026
264c10c
set up `VaList` global storage
folkertdev Jan 2, 2026
a0d6871
implement `va_arg` in `rustc_const_eval`
folkertdev Jan 2, 2026
883d140
basic support for `AllocKind::VaList` in miri
folkertdev Jan 2, 2026
3c22fc1
add c-variadic const eval test
folkertdev Jan 2, 2026
9830c16
basic `va_copy` implementation
folkertdev Jan 7, 2026
7eb7c91
move over to the shared interpreter
folkertdev Jan 8, 2026
08857bb
more copy UB tests
folkertdev Jan 20, 2026
d4f0534
stop using `reserve_and_set_va_list_alloc`
folkertdev Jan 22, 2026
6c68a71
make `va_list_map` private
folkertdev Jan 22, 2026
0f4af92
remove `GlobalAlloc::VaList` again
folkertdev Jan 22, 2026
b10a090
add (dead) allocation bookkeeping
folkertdev Jan 22, 2026
1f9b9e7
zero the `VaList` place so it is initialized
folkertdev Jan 22, 2026
d31c38b
add c-variadic miri test
folkertdev Jan 22, 2026
40b40ef
WIP
folkertdev Jan 22, 2026
17d7cf4
move va_list operations into `InterpCx`
folkertdev Jan 23, 2026
7aae090
`fn addr_from_alloc_id_uncached`: make va_list emit a dummy_alloc
folkertdev Jan 24, 2026
ce3a748
use places smarter
folkertdev Jan 24, 2026
8a49abe
move things around
folkertdev Jan 24, 2026
4915062
fix formatting
folkertdev Jan 24, 2026
7c4e3e0
rename to `self.va_list_ptr`
folkertdev Jan 24, 2026
bba1abe
add more miri tests
folkertdev Jan 24, 2026
d20c217
Apply suggestion from @RalfJung
folkertdev Jan 25, 2026
f9dbc2c
low-hanging fruit after review
folkertdev Jan 25, 2026
e0246b7
consider the caller location argument
folkertdev Jan 25, 2026
27f2fb1
use `transmute_copy`
folkertdev Jan 25, 2026
7832e24
split out `allocate_varargs`
folkertdev Jan 25, 2026
229f0cf
report UB when caller and callee signatures do not match
folkertdev Jan 28, 2026
4bc2318
retrieve key field without the lang item
folkertdev Jan 28, 2026
24b5666
feature-gate c-variadic definitions and calls in const contexts
folkertdev Jan 28, 2026
03543fb
clean up feature gate
folkertdev Jan 29, 2026
cc7d5b5
update tests
folkertdev Jan 29, 2026
5e6519c
use vecdeque
folkertdev Jan 29, 2026
49ba67a
changes after code review
folkertdev Jan 31, 2026
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
4 changes: 0 additions & 4 deletions compiler/rustc_ast_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ ast_passes_c_variadic_no_extern = `...` is not supported for non-extern function
ast_passes_c_variadic_not_supported = the `{$target}` target does not support c-variadic functions
ast_passes_const_and_c_variadic = functions cannot be both `const` and C-variadic
.const = `const` because of this
.variadic = C-variadic because of this
ast_passes_const_and_coroutine = functions cannot be both `const` and `{$coroutine_kind}`
.const = `const` because of this
.coroutine = `{$coroutine_kind}` because of this
Expand Down
12 changes: 5 additions & 7 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,13 +698,11 @@ impl<'a> AstValidator<'a> {
unreachable!("C variable argument list cannot be used in closures")
};

// C-variadics are not yet implemented in const evaluation.
if let Const::Yes(const_span) = sig.header.constness {
self.dcx().emit_err(errors::ConstAndCVariadic {
spans: vec![const_span, variadic_param.span],
const_span,
variadic_span: variadic_param.span,
});
if let Const::Yes(_) = sig.header.constness
&& !self.features.enabled(sym::const_c_variadic)
{
let msg = format!("c-variadic const function definitions are unstable");
feature_err(&self.sess, sym::const_c_variadic, sig.span, msg).emit();
}

if let Some(coroutine_kind) = sig.header.coroutine_kind {
Expand Down
11 changes: 0 additions & 11 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,17 +710,6 @@ pub(crate) struct ConstAndCoroutine {
pub coroutine_kind: &'static str,
}

#[derive(Diagnostic)]
#[diag(ast_passes_const_and_c_variadic)]
pub(crate) struct ConstAndCVariadic {
#[primary_span]
pub spans: Vec<Span>,
#[label(ast_passes_const)]
pub const_span: Span,
#[label(ast_passes_variadic)]
pub variadic_span: Span,
}

#[derive(Diagnostic)]
#[diag(ast_passes_coroutine_and_c_variadic)]
pub(crate) struct CoroutineAndCVariadic {
Expand Down
17 changes: 17 additions & 0 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ const_eval_bad_pointer_op_attempting = {const_eval_bad_pointer_op}: {$operation

const_eval_bounds_check_failed =
indexing out of bounds: the len is {$len} but the index is {$index}

const_eval_c_variadic_call =
calling const c-variadic functions is unstable in {const_eval_const_context}s

const_eval_c_variadic_fixed_count_mismatch =
calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee}

const_eval_c_variadic_mismatch =
calling a function where the caller and callee disagree on whether the function is C-variadic

const_eval_call_nonzero_intrinsic =
`{$name}` called on 0

Expand Down Expand Up @@ -91,6 +101,8 @@ const_eval_deref_function_pointer =
accessing {$allocation} which contains a function
const_eval_deref_typeid_pointer =
accessing {$allocation} which contains a `TypeId`
const_eval_deref_va_list_pointer =
accessing {$allocation} which contains a variable argument list
const_eval_deref_vtable_pointer =
accessing {$allocation} which contains a vtable
const_eval_division_by_zero =
Expand Down Expand Up @@ -195,6 +207,9 @@ const_eval_invalid_uninit_bytes =
const_eval_invalid_uninit_bytes_unknown =
using uninitialized data, but this operation requires initialized memory

const_eval_invalid_va_list_pointer =
using {$pointer} as variable argument list pointer but it does not point to a variable argument list

const_eval_invalid_vtable_pointer =
using {$pointer} as vtable pointer but it does not point to a vtable

Expand Down Expand Up @@ -430,6 +445,8 @@ const_eval_unterminated_c_string =
const_eval_unwind_past_top =
unwinding past the topmost frame of the stack

const_eval_va_arg_out_of_bounds = more C-variadic arguments read than were passed

## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`.
## (We'd love to sort this differently to make that more clear but tidy won't let us...)
const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty}
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_const_eval/src/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
});
}

if self.tcx.fn_sig(callee).skip_binder().c_variadic() {
self.check_op(ops::FnCallCVariadic)
}

// At this point, we are calling a function, `callee`, whose `DefId` is known...

// `begin_panic` and `panic_display` functions accept generic
Expand Down
21 changes: 21 additions & 0 deletions compiler/rustc_const_eval/src/check_consts/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
}
}

/// A c-variadic function call.
#[derive(Debug)]
pub(crate) struct FnCallCVariadic;
impl<'tcx> NonConstOp<'tcx> for FnCallCVariadic {
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable {
gate: sym::const_c_variadic,
gate_already_checked: false,
safe_to_expose_on_stable: false,
is_function_call: true,
}
}

fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.tcx.sess.create_feature_err(
errors::NonConstCVariadicCall { span, kind: ccx.const_kind() },
sym::const_c_variadic,
)
}
}

/// A call to a function that is in a trait, or has trait bounds that make it conditionally-const.
#[derive(Debug)]
pub(crate) struct ConditionallyConstCall<'tcx> {
Expand Down
29 changes: 28 additions & 1 deletion compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@ pub struct NonConstClosure {
pub non_or_conditionally: &'static str,
}

#[derive(Diagnostic)]
#[diag(const_eval_c_variadic_call, code = E0015)]
pub struct NonConstCVariadicCall {
#[primary_span]
pub span: Span,
pub kind: ConstContext,
}

#[derive(Subdiagnostic)]
pub enum NonConstClosureNote {
#[note(const_eval_closure_fndef_not_const)]
Expand Down Expand Up @@ -492,11 +500,13 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
WriteToReadOnly(_) => const_eval_write_to_read_only,
DerefFunctionPointer(_) => const_eval_deref_function_pointer,
DerefVTablePointer(_) => const_eval_deref_vtable_pointer,
DerefVaListPointer(_) => const_eval_deref_va_list_pointer,
DerefTypeIdPointer(_) => const_eval_deref_typeid_pointer,
InvalidBool(_) => const_eval_invalid_bool,
InvalidChar(_) => const_eval_invalid_char,
InvalidTag(_) => const_eval_invalid_tag,
InvalidFunctionPointer(_) => const_eval_invalid_function_pointer,
InvalidVaListPointer(_) => const_eval_invalid_va_list_pointer,
InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
InvalidVTableTrait { .. } => const_eval_invalid_vtable_trait,
InvalidStr(_) => const_eval_invalid_str,
Expand All @@ -509,8 +519,12 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
InvalidNichedEnumVariantWritten { .. } => {
const_eval_invalid_niched_enum_variant_written
}
VaArgOutOfBounds => const_eval_va_arg_out_of_bounds,

AbiMismatchArgument { .. } => const_eval_incompatible_arg_types,
AbiMismatchReturn { .. } => const_eval_incompatible_return_types,
CVariadicMismatch { .. } => const_eval_c_variadic_mismatch,
CVariadicFixedCountMismatch { .. } => const_eval_c_variadic_fixed_count_mismatch,
}
}

Expand All @@ -535,6 +549,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
| InvalidMeta(InvalidMetaKind::TooBig)
| InvalidUninitBytes(None)
| DeadLocal
| VaArgOutOfBounds
| UninhabitedEnumVariantWritten(_)
| UninhabitedEnumVariantRead(_) => {}

Expand All @@ -555,7 +570,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
diag.arg("len", len);
diag.arg("index", index);
}
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
UnterminatedCString(ptr)
| InvalidFunctionPointer(ptr)
| InvalidVTablePointer(ptr)
| InvalidVaListPointer(ptr) => {
diag.arg("pointer", ptr);
}
InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => {
Expand Down Expand Up @@ -609,6 +627,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
WriteToReadOnly(alloc)
| DerefFunctionPointer(alloc)
| DerefVTablePointer(alloc)
| DerefVaListPointer(alloc)
| DerefTypeIdPointer(alloc) => {
diag.arg("allocation", alloc);
}
Expand Down Expand Up @@ -645,6 +664,14 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
diag.arg("caller_ty", caller_ty);
diag.arg("callee_ty", callee_ty);
}
CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => {
diag.arg("caller_is_c_variadic", caller_is_c_variadic);
diag.arg("callee_is_c_variadic", callee_is_c_variadic);
}
CVariadicFixedCountMismatch { caller, callee } => {
diag.arg("caller", caller);
diag.arg("callee", callee);
}
}
}
}
Expand Down
87 changes: 77 additions & 10 deletions compiler/rustc_const_eval/src/interpret/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::borrow::Cow;
use either::{Left, Right};
use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx};
use rustc_data_structures::assert_matches;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::layout::{IntegerExt, TyAndLayout};
use rustc_middle::ty::{self, AdtDef, Instance, Ty, VariantDef};
Expand All @@ -17,7 +18,7 @@ use tracing::{info, instrument, trace};
use super::{
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy,
Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok,
throw_ub, throw_ub_custom, throw_unsup_format,
throw_ub, throw_ub_custom,
};
use crate::interpret::EnteredTraceSpan;
use crate::{enter_trace_span, fluent_generated as fluent};
Expand Down Expand Up @@ -349,13 +350,27 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
) -> InterpResult<'tcx> {
let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty);

// Compute callee information.
// FIXME: for variadic support, do we have to somehow determine callee's extra_args?
let callee_fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
// The check for the DefKind is so that we don't request the fn_sig of a closure.
// Otherwise, we hit:
//
// DefId(1:180 ~ std[269c]::rt::lang_start_internal::{closure#0}) does not have a "fn_sig"
let (fixed_count, callee_c_variadic_args) =
if matches!(self.tcx.def_kind(instance.def_id()), DefKind::Fn)
&& let callee_fn_sig = self.tcx.fn_sig(instance.def_id()).skip_binder()
&& callee_fn_sig.c_variadic()
Comment on lines +353 to +360
Copy link
Member

Choose a reason for hiding this comment

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

This is very odd and seems to break our usual abstractions. How does codegen deal with this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure, I asked in #t-compiler/help > no `fn_sig`for `lang_start_internal` closure. My impression is that those functions just never get fn_sig called on them, but maybe there is some logic that I'm missing somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently checking for the DefKind is fairly standard

{
// A mismatch in caller and callee fixed_count will error below.
let fixed_count = callee_fn_sig.inputs().skip_binder().len();
let extra_tys = caller_fn_abi.args[caller_fn_abi.fixed_count as usize..]
.iter()
.map(|arg_abi| arg_abi.layout.ty);

(fixed_count, self.tcx.mk_type_list_from_iter(extra_tys))
} else {
(caller_fn_abi.fixed_count.try_into().unwrap(), ty::List::empty())
};

if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
throw_unsup_format!("calling a c-variadic function is not supported");
}
let callee_fn_abi = self.fn_abi_of_instance(instance, callee_c_variadic_args)?;

if caller_fn_abi.conv != callee_fn_abi.conv {
throw_ub_custom!(
Expand All @@ -365,6 +380,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
)
}

if caller_fn_abi.c_variadic != callee_fn_abi.c_variadic {
throw_ub!(CVariadicMismatch {
caller_is_c_variadic: caller_fn_abi.c_variadic,
callee_is_c_variadic: callee_fn_abi.c_variadic,
});
}

if caller_fn_abi.c_variadic && caller_fn_abi.fixed_count != callee_fn_abi.fixed_count {
throw_ub!(CVariadicFixedCountMismatch {
caller: caller_fn_abi.fixed_count,
callee: callee_fn_abi.fixed_count,
});
}

// Check that all target features required by the callee (i.e., from
// the attribute `#[target_feature(enable = ...)]`) are enabled at
// compile time.
Expand Down Expand Up @@ -436,16 +465,54 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// this is a single iterator (that handles `spread_arg`), then
// `pass_argument` would be the loop body. It takes care to
// not advance `caller_iter` for ignored arguments.
let mut callee_args_abis = callee_fn_abi.args.iter().enumerate();
for local in body.args_iter() {
let mut callee_args_abis = if caller_fn_abi.c_variadic {
// Only the fixed arguments are passed normally. C-variadic functions cannot be
// `extern "Rust"` and `#[track_caller]` can only be applied to `extern "Rust"`, to
// the extra caller location argument is not relevant here.
assert!(!instance.def.requires_caller_location(*self.tcx));
callee_fn_abi.args[..fixed_count].iter().enumerate()
} else {
// NOTE: this handles the extra caller location argument that is passed when
// `#[track_caller]` is used. The `fixed_count` does not account for this argument.
// This attribute is only allowed on `extern "Rust"` functions, so the c-variadic
// case does not need to handle the extra argument.
callee_fn_abi.args.iter().enumerate()
};

let mut it = body.args_iter().peekable();
while let Some(local) = it.next() {
// Construct the destination place for this argument. At this point all
// locals are still dead, so we cannot construct a `PlaceTy`.
let dest = mir::Place::from(local);
// `layout_of_local` does more than just the instantiation we need to get the
// type, but the result gets cached so this avoids calling the instantiation
// query *again* the next time this local is accessed.
let ty = self.layout_of_local(self.frame(), local, None)?.ty;
if Some(local) == body.spread_arg {
if caller_fn_abi.c_variadic && it.peek().is_none() {
// This is the last callee-side argument of a variadic function.
// This argument is a VaList holding the remaining caller-side arguments.
self.storage_live(local)?;

let place = self.eval_place(dest)?;
let mplace = self.force_allocation(&place)?;

// Consume the remaining arguments by putting them into the variable argument
// list.
let varargs = self.allocate_varargs(&mut caller_args)?;
// When the frame is dropped, these variable arguments are deallocated.
self.frame_mut().va_list = varargs.clone();
let key = self.va_list_ptr(varargs.into());

// Zero the VaList, so it is fully initialized.
self.write_bytes_ptr(
mplace.ptr(),
(0..mplace.layout.size.bytes()).map(|_| 0u8),
)?;

// Store the "key" pointer in the right field.
let key_mplace = self.va_list_key_field(&mplace)?;
self.write_pointer(key, &key_mplace)?;
} else if Some(local) == body.spread_arg {
// Make the local live once, then fill in the value field by field.
self.storage_live(local)?;
// Must be a tuple
Expand Down
Loading
Loading