Skip to content

Commit 494c6da

Browse files
authored
Rollup merge of #150601 - folkertdev:c-variadic-const-eval, r=RalfJung
support c-variadic functions in `rustc_const_eval` tracking issue: #44930 The new `GlobalAlloc::VaList` is used to create an `AllocId` that represents the variable argument list of a frame. The allocation itself does not store any data, all we need is the unique identifier. The actual variable argument list is stored in `Memory`, and keyed by the `AllocId`. The `Frame` also stores this `AllocId`, so that when a frame is popped, it can deallocate the variable arguments. At "runtime" a `VaList` value stores a pointer to the global allocation in its first bytes. The provenance on this pointer can be used to retrieve its `AllocId`, and the offset of the pointer is used to store the index of the next argument to read from the variable argument list. Miri does not yet support `va_arg`, but I think that can be done separetely? r? @RalfJung cc @workingjubilee
2 parents bff3e9e + 981dacc commit 494c6da

33 files changed

Lines changed: 1306 additions & 72 deletions

File tree

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -698,13 +698,11 @@ impl<'a> AstValidator<'a> {
698698
unreachable!("C variable argument list cannot be used in closures")
699699
};
700700

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

710708
if let Some(coroutine_kind) = sig.header.coroutine_kind {

compiler/rustc_ast_passes/src/errors.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -823,17 +823,6 @@ pub(crate) struct ConstAndCoroutine {
823823
pub coroutine_kind: &'static str,
824824
}
825825

826-
#[derive(Diagnostic)]
827-
#[diag("functions cannot be both `const` and C-variadic")]
828-
pub(crate) struct ConstAndCVariadic {
829-
#[primary_span]
830-
pub spans: Vec<Span>,
831-
#[label("`const` because of this")]
832-
pub const_span: Span,
833-
#[label("C-variadic because of this")]
834-
pub variadic_span: Span,
835-
}
836-
837826
#[derive(Diagnostic)]
838827
#[diag("functions cannot be both `{$coroutine_kind}` and C-variadic")]
839828
pub(crate) struct CoroutineAndCVariadic {

compiler/rustc_const_eval/src/check_consts/check.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
815815
});
816816
}
817817

818+
if self.tcx.fn_sig(callee).skip_binder().c_variadic() {
819+
self.check_op(ops::FnCallCVariadic)
820+
}
821+
818822
// At this point, we are calling a function, `callee`, whose `DefId` is known...
819823

820824
// `begin_panic` and `panic_display` functions accept generic

compiler/rustc_const_eval/src/check_consts/ops.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
7575
}
7676
}
7777

78+
/// A c-variadic function call.
79+
#[derive(Debug)]
80+
pub(crate) struct FnCallCVariadic;
81+
impl<'tcx> NonConstOp<'tcx> for FnCallCVariadic {
82+
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
83+
Status::Unstable {
84+
gate: sym::const_c_variadic,
85+
gate_already_checked: false,
86+
safe_to_expose_on_stable: false,
87+
is_function_call: true,
88+
}
89+
}
90+
91+
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
92+
ccx.tcx.sess.create_feature_err(
93+
errors::NonConstCVariadicCall { span, kind: ccx.const_kind() },
94+
sym::const_c_variadic,
95+
)
96+
}
97+
}
98+
7899
/// A call to a function that is in a trait, or has trait bounds that make it conditionally-const.
79100
#[derive(Debug)]
80101
pub(crate) struct ConditionallyConstCall<'tcx> {

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_errors::msg;
99
use rustc_hir::def_id::{DefId, LocalDefId};
1010
use rustc_hir::{self as hir, CRATE_HIR_ID, LangItem};
1111
use rustc_middle::mir::AssertMessage;
12-
use rustc_middle::mir::interpret::{Pointer, ReportedErrorInfo};
12+
use rustc_middle::mir::interpret::ReportedErrorInfo;
1313
use rustc_middle::query::TyCtxtAt;
1414
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout, ValidityRequirement};
1515
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -22,7 +22,7 @@ use super::error::*;
2222
use crate::errors::{LongRunning, LongRunningWarn};
2323
use crate::interpret::{
2424
self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
25-
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar,
25+
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar,
2626
compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub,
2727
throw_ub_custom, throw_unsup, throw_unsup_format,
2828
};

compiler/rustc_const_eval/src/errors.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,19 @@ pub struct NonConstClosure {
531531
pub non_or_conditionally: &'static str,
532532
}
533533

534+
#[derive(Diagnostic)]
535+
#[diag(r#"calling const c-variadic functions is unstable in {$kind ->
536+
[const] constant
537+
[static] static
538+
[const_fn] constant function
539+
*[other] {""}
540+
}s"#, code = E0015)]
541+
pub struct NonConstCVariadicCall {
542+
#[primary_span]
543+
pub span: Span,
544+
pub kind: ConstContext,
545+
}
546+
534547
#[derive(Subdiagnostic)]
535548
pub enum NonConstClosureNote {
536549
#[note("function defined here, but it is not `const`")]
@@ -757,11 +770,13 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
757770
WriteToReadOnly(_) => msg!("writing to {$allocation} which is read-only"),
758771
DerefFunctionPointer(_) => msg!("accessing {$allocation} which contains a function"),
759772
DerefVTablePointer(_) => msg!("accessing {$allocation} which contains a vtable"),
773+
DerefVaListPointer(_) => msg!("accessing {$allocation} which contains a variable argument list"),
760774
DerefTypeIdPointer(_) => msg!("accessing {$allocation} which contains a `TypeId`"),
761775
InvalidBool(_) => msg!("interpreting an invalid 8-bit value as a bool: 0x{$value}"),
762776
InvalidChar(_) => msg!("interpreting an invalid 32-bit value as a char: 0x{$value}"),
763777
InvalidTag(_) => msg!("enum value has invalid tag: {$tag}"),
764778
InvalidFunctionPointer(_) => msg!("using {$pointer} as function pointer but it does not point to a function"),
779+
InvalidVaListPointer(_) => msg!("using {$pointer} as variable argument list pointer but it does not point to a variable argument list"),
765780
InvalidVTablePointer(_) => msg!("using {$pointer} as vtable pointer but it does not point to a vtable"),
766781
InvalidVTableTrait { .. } => msg!("using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected"),
767782
InvalidStr(_) => msg!("this string is not valid UTF-8: {$err}"),
@@ -776,6 +791,9 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
776791
}
777792
AbiMismatchArgument { .. } => msg!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"),
778793
AbiMismatchReturn { .. } => msg!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"),
794+
VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(),
795+
CVariadicMismatch { ..} => "calling a function where the caller and callee disagree on whether the function is C-variadic".into(),
796+
CVariadicFixedCountMismatch { .. } => msg!("calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee}"),
779797
}
780798
}
781799

@@ -800,6 +818,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
800818
| InvalidMeta(InvalidMetaKind::TooBig)
801819
| InvalidUninitBytes(None)
802820
| DeadLocal
821+
| VaArgOutOfBounds
803822
| UninhabitedEnumVariantWritten(_)
804823
| UninhabitedEnumVariantRead(_) => {}
805824

@@ -820,7 +839,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
820839
diag.arg("len", len);
821840
diag.arg("index", index);
822841
}
823-
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
842+
UnterminatedCString(ptr)
843+
| InvalidFunctionPointer(ptr)
844+
| InvalidVaListPointer(ptr)
845+
| InvalidVTablePointer(ptr) => {
824846
diag.arg("pointer", ptr);
825847
}
826848
InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => {
@@ -874,6 +896,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
874896
WriteToReadOnly(alloc)
875897
| DerefFunctionPointer(alloc)
876898
| DerefVTablePointer(alloc)
899+
| DerefVaListPointer(alloc)
877900
| DerefTypeIdPointer(alloc) => {
878901
diag.arg("allocation", alloc);
879902
}
@@ -910,6 +933,14 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
910933
diag.arg("caller_ty", caller_ty);
911934
diag.arg("callee_ty", callee_ty);
912935
}
936+
CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => {
937+
diag.arg("caller_is_c_variadic", caller_is_c_variadic);
938+
diag.arg("callee_is_c_variadic", callee_is_c_variadic);
939+
}
940+
CVariadicFixedCountMismatch { caller, callee } => {
941+
diag.arg("caller", caller);
942+
diag.arg("callee", callee);
943+
}
913944
}
914945
}
915946
}

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use tracing::{info, instrument, trace};
1919
use super::{
2020
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy,
2121
Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok,
22-
throw_ub, throw_ub_custom, throw_unsup_format,
22+
throw_ub, throw_ub_custom,
2323
};
2424
use crate::enter_trace_span;
2525
use crate::interpret::EnteredTraceSpan;
@@ -354,13 +354,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
354354
) -> InterpResult<'tcx> {
355355
let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty);
356356

357-
// Compute callee information.
358-
// FIXME: for variadic support, do we have to somehow determine callee's extra_args?
359-
let callee_fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
360-
361-
if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
362-
throw_unsup_format!("calling a c-variadic function is not supported");
363-
}
357+
// The first order of business is to figure out the callee signature.
358+
// However, that requires the list of variadic arguments.
359+
// We use the *caller* information to determine where to split the list of arguments,
360+
// and then later check that the callee indeed has the same number of fixed arguments.
361+
let extra_tys = if caller_fn_abi.c_variadic {
362+
let fixed_count = usize::try_from(caller_fn_abi.fixed_count).unwrap();
363+
let extra_tys = args[fixed_count..].iter().map(|arg| arg.layout().ty);
364+
self.tcx.mk_type_list_from_iter(extra_tys)
365+
} else {
366+
ty::List::empty()
367+
};
368+
let callee_fn_abi = self.fn_abi_of_instance(instance, extra_tys)?;
364369

365370
if caller_fn_abi.conv != callee_fn_abi.conv {
366371
throw_ub_custom!(
@@ -372,6 +377,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
372377
)
373378
}
374379

380+
if caller_fn_abi.c_variadic != callee_fn_abi.c_variadic {
381+
throw_ub!(CVariadicMismatch {
382+
caller_is_c_variadic: caller_fn_abi.c_variadic,
383+
callee_is_c_variadic: callee_fn_abi.c_variadic,
384+
});
385+
}
386+
if caller_fn_abi.c_variadic && caller_fn_abi.fixed_count != callee_fn_abi.fixed_count {
387+
throw_ub!(CVariadicFixedCountMismatch {
388+
caller: caller_fn_abi.fixed_count,
389+
callee: callee_fn_abi.fixed_count,
390+
});
391+
}
392+
375393
// Check that all target features required by the callee (i.e., from
376394
// the attribute `#[target_feature(enable = ...)]`) are enabled at
377395
// compile time.
@@ -444,6 +462,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
444462
// `pass_argument` would be the loop body. It takes care to
445463
// not advance `caller_iter` for ignored arguments.
446464
let mut callee_args_abis = callee_fn_abi.args.iter().enumerate();
465+
// Determine whether there is a special VaList argument. This is always the
466+
// last argument, and since arguments start at index 1 that's `arg_count`.
467+
let va_list_arg =
468+
callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count));
447469
for local in body.args_iter() {
448470
// Construct the destination place for this argument. At this point all
449471
// locals are still dead, so we cannot construct a `PlaceTy`.
@@ -452,7 +474,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
452474
// type, but the result gets cached so this avoids calling the instantiation
453475
// query *again* the next time this local is accessed.
454476
let ty = self.layout_of_local(self.frame(), local, None)?.ty;
455-
if Some(local) == body.spread_arg {
477+
if Some(local) == va_list_arg {
478+
// This is the last callee-side argument of a variadic function.
479+
// This argument is a VaList holding the remaining caller-side arguments.
480+
self.storage_live(local)?;
481+
482+
let place = self.eval_place(dest)?;
483+
let mplace = self.force_allocation(&place)?;
484+
485+
// Consume the remaining arguments by putting them into the variable argument
486+
// list.
487+
let varargs = self.allocate_varargs(&mut caller_args, &mut callee_args_abis)?;
488+
// When the frame is dropped, these variable arguments are deallocated.
489+
self.frame_mut().va_list = varargs.clone();
490+
let key = self.va_list_ptr(varargs.into());
491+
492+
// Zero the VaList, so it is fully initialized.
493+
self.write_bytes_ptr(
494+
mplace.ptr(),
495+
(0..mplace.layout.size.bytes()).map(|_| 0u8),
496+
)?;
497+
498+
// Store the "key" pointer in the right field.
499+
let key_mplace = self.va_list_key_field(&mplace)?;
500+
self.write_pointer(key, &key_mplace)?;
501+
} else if Some(local) == body.spread_arg {
456502
// Make the local live once, then fill in the value field by field.
457503
self.storage_live(local)?;
458504
// Must be a tuple
@@ -491,7 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
491537
if instance.def.requires_caller_location(*self.tcx) {
492538
callee_args_abis.next().unwrap();
493539
}
494-
// Now we should have no more caller args or callee arg ABIs
540+
// Now we should have no more caller args or callee arg ABIs.
495541
assert!(
496542
callee_args_abis.next().is_none(),
497543
"mismatch between callee ABI and callee body arguments"

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use super::memory::MemoryKind;
2323
use super::util::ensure_monomorphic_enough;
2424
use super::{
2525
AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer,
26-
PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval,
27-
throw_ub_custom, throw_ub_format,
26+
PointerArithmetic, Projectable, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok,
27+
throw_inval, throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format,
2828
};
2929
use crate::interpret::Writeable;
3030

@@ -750,6 +750,57 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
750750
self.float_muladd_intrinsic::<Quad>(args, dest, MulAddType::Nondeterministic)?
751751
}
752752

753+
sym::va_copy => {
754+
let va_list = self.deref_pointer(&args[0])?;
755+
let key_mplace = self.va_list_key_field(&va_list)?;
756+
let key = self.read_pointer(&key_mplace)?;
757+
758+
let varargs = self.get_ptr_va_list(key)?;
759+
let copy_key = self.va_list_ptr(varargs.clone());
760+
761+
let copy_key_mplace = self.va_list_key_field(dest)?;
762+
self.write_pointer(copy_key, &copy_key_mplace)?;
763+
}
764+
765+
sym::va_end => {
766+
let va_list = self.deref_pointer(&args[0])?;
767+
let key_mplace = self.va_list_key_field(&va_list)?;
768+
let key = self.read_pointer(&key_mplace)?;
769+
770+
self.deallocate_va_list(key)?;
771+
}
772+
773+
sym::va_arg => {
774+
let va_list = self.deref_pointer(&args[0])?;
775+
let key_mplace = self.va_list_key_field(&va_list)?;
776+
let key = self.read_pointer(&key_mplace)?;
777+
778+
// Invalidate the old list and get its content. We'll recreate the
779+
// new list (one element shorter) below.
780+
let mut varargs = self.deallocate_va_list(key)?;
781+
782+
let Some(arg_mplace) = varargs.pop_front() else {
783+
throw_ub!(VaArgOutOfBounds);
784+
};
785+
786+
// NOTE: In C some type conversions are allowed (e.g. casting between signed and
787+
// unsigned integers). For now we require c-variadic arguments to be read with the
788+
// exact type they were passed as.
789+
if arg_mplace.layout.ty != dest.layout.ty {
790+
throw_unsup_format!(
791+
"va_arg type mismatch: requested `{}`, but next argument is `{}`",
792+
dest.layout.ty,
793+
arg_mplace.layout.ty
794+
);
795+
}
796+
// Copy the argument.
797+
self.copy_op(&arg_mplace, dest)?;
798+
799+
// Update the VaList pointer.
800+
let new_key = self.va_list_ptr(varargs);
801+
self.write_pointer(new_key, &key_mplace)?;
802+
}
803+
753804
// Unsupported intrinsic: skip the return_to_block below.
754805
_ => return interp_ok(false),
755806
}
@@ -1230,4 +1281,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
12301281
interp_ok(Some(ImmTy::from_scalar(val, cast_to)))
12311282
}
12321283
}
1284+
1285+
/// Get the MPlace of the key from the place storing the VaList.
1286+
pub(super) fn va_list_key_field<P: Projectable<'tcx, M::Provenance>>(
1287+
&self,
1288+
va_list: &P,
1289+
) -> InterpResult<'tcx, P> {
1290+
// The struct wrapped by VaList.
1291+
let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?;
1292+
1293+
// Find the first pointer field in this struct. The exact index is target-specific.
1294+
let ty::Adt(adt, substs) = va_list_inner.layout().ty.kind() else {
1295+
bug!("invalid VaListImpl layout");
1296+
};
1297+
1298+
for (i, field) in adt.non_enum_variant().fields.iter().enumerate() {
1299+
if field.ty(*self.tcx, substs).is_raw_ptr() {
1300+
return self.project_field(&va_list_inner, FieldIdx::from_usize(i));
1301+
}
1302+
}
1303+
1304+
bug!("no VaListImpl field is a pointer");
1305+
}
12331306
}

0 commit comments

Comments
 (0)