Skip to content

Commit 02c4af3

Browse files
committed
c-variadic functions in rustc_const_eval
1 parent ce69380 commit 02c4af3

16 files changed

Lines changed: 908 additions & 33 deletions

File tree

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
757757
WriteToReadOnly(_) => msg!("writing to {$allocation} which is read-only"),
758758
DerefFunctionPointer(_) => msg!("accessing {$allocation} which contains a function"),
759759
DerefVTablePointer(_) => msg!("accessing {$allocation} which contains a vtable"),
760+
DerefVaListPointer(_) => msg!("accessing {$allocation} which contains a variable argument list"),
760761
DerefTypeIdPointer(_) => msg!("accessing {$allocation} which contains a `TypeId`"),
761762
InvalidBool(_) => msg!("interpreting an invalid 8-bit value as a bool: 0x{$value}"),
762763
InvalidChar(_) => msg!("interpreting an invalid 32-bit value as a char: 0x{$value}"),
@@ -776,6 +777,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
776777
}
777778
AbiMismatchArgument { .. } => msg!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"),
778779
AbiMismatchReturn { .. } => msg!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"),
780+
VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(),
779781
}
780782
}
781783

@@ -800,6 +802,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
800802
| InvalidMeta(InvalidMetaKind::TooBig)
801803
| InvalidUninitBytes(None)
802804
| DeadLocal
805+
| VaArgOutOfBounds
803806
| UninhabitedEnumVariantWritten(_)
804807
| UninhabitedEnumVariantRead(_) => {}
805808

@@ -874,6 +877,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
874877
WriteToReadOnly(alloc)
875878
| DerefFunctionPointer(alloc)
876879
| DerefVTablePointer(alloc)
880+
| DerefVaListPointer(alloc)
877881
| DerefTypeIdPointer(alloc) => {
878882
diag.arg("allocation", alloc);
879883
}

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use std::borrow::Cow;
55

66
use either::{Left, Right};
7-
use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx};
7+
use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, VariantIdx};
88
use rustc_data_structures::assert_matches;
99
use rustc_errors::msg;
1010
use rustc_hir::def_id::DefId;
@@ -17,9 +17,9 @@ use tracing::field::Empty;
1717
use tracing::{info, instrument, trace};
1818

1919
use super::{
20-
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy,
21-
Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok,
22-
throw_ub, throw_ub_custom, throw_unsup_format,
20+
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy,
21+
PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo,
22+
interp_ok, throw_ub, throw_ub_custom,
2323
};
2424
use crate::enter_trace_span;
2525
use crate::interpret::EnteredTraceSpan;
@@ -354,12 +354,22 @@ 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())?;
357+
let (fixed_count, c_variadic_args) = if caller_fn_abi.c_variadic {
358+
let sig = self.tcx.fn_sig(instance.def_id()).skip_binder();
359+
let fixed_count = sig.inputs().skip_binder().len();
360+
assert!(caller_fn_abi.args.len() >= fixed_count);
361+
let extra_tys: Vec<Ty<'tcx>> =
362+
caller_fn_abi.args[fixed_count..].iter().map(|arg_abi| arg_abi.layout.ty).collect();
360363

361-
if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
362-
throw_unsup_format!("calling a c-variadic function is not supported");
364+
(fixed_count, self.tcx.mk_type_list(&extra_tys))
365+
} else {
366+
(caller_fn_abi.args.len(), ty::List::empty())
367+
};
368+
369+
let callee_fn_abi = self.fn_abi_of_instance(instance, c_variadic_args)?;
370+
371+
if callee_fn_abi.c_variadic ^ caller_fn_abi.c_variadic {
372+
unreachable!("caller and callee disagree on being c-variadic");
363373
}
364374

365375
if caller_fn_abi.conv != callee_fn_abi.conv {
@@ -443,16 +453,60 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
443453
// this is a single iterator (that handles `spread_arg`), then
444454
// `pass_argument` would be the loop body. It takes care to
445455
// not advance `caller_iter` for ignored arguments.
446-
let mut callee_args_abis = callee_fn_abi.args.iter().enumerate();
447-
for local in body.args_iter() {
456+
let mut callee_args_abis = if caller_fn_abi.c_variadic {
457+
callee_fn_abi.args[..fixed_count].iter().enumerate()
458+
} else {
459+
callee_fn_abi.args.iter().enumerate()
460+
};
461+
462+
let mut it = body.args_iter().peekable();
463+
while let Some(local) = it.next() {
448464
// Construct the destination place for this argument. At this point all
449465
// locals are still dead, so we cannot construct a `PlaceTy`.
450466
let dest = mir::Place::from(local);
451467
// `layout_of_local` does more than just the instantiation we need to get the
452468
// type, but the result gets cached so this avoids calling the instantiation
453469
// query *again* the next time this local is accessed.
454470
let ty = self.layout_of_local(self.frame(), local, None)?.ty;
455-
if Some(local) == body.spread_arg {
471+
if caller_fn_abi.c_variadic && it.peek().is_none() {
472+
// The callee's signature has an additional VaList argument, that the caller
473+
// won't actually pass. Here we synthesize a `VaList` value, whose leading bytes
474+
// are a pointer that can be mapped to the corresponding variable argument list.
475+
self.storage_live(local)?;
476+
477+
let place = self.eval_place(dest)?;
478+
let mplace = self.force_allocation(&place)?;
479+
480+
// Consume the remaining arguments and store them in a global allocation.
481+
let mut varargs = Vec::new();
482+
for (fn_arg, abi) in &mut caller_args {
483+
let op = self.copy_fn_arg(fn_arg);
484+
let mplace = self.allocate(abi.layout, MemoryKind::Stack)?;
485+
self.copy_op(&op, &mplace)?;
486+
487+
varargs.push(mplace);
488+
}
489+
490+
// When the frame is dropped, this ID is used to deallocate the variable arguments list.
491+
self.frame_mut().va_list = varargs.clone();
492+
493+
// This is a new VaList, so start at index 0.
494+
let ptr = self.va_list_ptr(varargs, 0);
495+
let addr = Scalar::from_pointer(ptr, self);
496+
497+
// Zero the mplace, so it is fully initialized.
498+
self.write_bytes_ptr(
499+
mplace.ptr(),
500+
(0..mplace.layout.size.bytes()).map(|_| 0u8),
501+
)?;
502+
503+
// Store the pointer to the global variable arguments list allocation in the
504+
// first bytes of the `VaList` value.
505+
let mut alloc = self
506+
.get_ptr_alloc_mut(mplace.ptr(), self.data_layout().pointer_size())?
507+
.expect("not a ZST");
508+
alloc.write_ptr_sized(Size::ZERO, addr)?;
509+
} else if Some(local) == body.spread_arg {
456510
// Make the local live once, then fill in the value field by field.
457511
self.storage_live(local)?;
458512
// Must be a tuple

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use super::util::ensure_monomorphic_enough;
2424
use super::{
2525
AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer,
2626
PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval,
27-
throw_ub_custom, throw_ub_format,
27+
throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format,
2828
};
2929
use crate::interpret::Writeable;
3030

@@ -750,6 +750,116 @@ 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+
// fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f>
755+
let src_ptr = self.read_pointer(&args[0])?;
756+
757+
// Read the token pointer from the src VaList (alloc_id + offset-as-index).
758+
let src_va_list_ptr = {
759+
let pointer_size = tcx.data_layout.pointer_size();
760+
let alloc = self
761+
.get_ptr_alloc(src_ptr, pointer_size)?
762+
.expect("va_list storage should not be a ZST");
763+
let scalar = alloc.read_pointer(Size::ZERO)?;
764+
scalar.to_pointer(self)?
765+
};
766+
767+
let (prov, offset) = src_va_list_ptr.into_raw_parts();
768+
let src_alloc_id = prov.unwrap().get_alloc_id().unwrap();
769+
let index = offset.bytes();
770+
771+
// Look up arguments without consuming src.
772+
let Some(arguments) = self.get_va_list_alloc(src_alloc_id) else {
773+
throw_unsup_format!("va_copy on unknown va_list allocation {:?}", src_alloc_id);
774+
};
775+
776+
// Create a new allocation pointing at the same index.
777+
let new_va_list_ptr = self.va_list_ptr(arguments.to_vec(), index);
778+
let addr = Scalar::from_pointer(new_va_list_ptr, self);
779+
780+
// Now overwrite the token pointer stored inside the VaList.
781+
let mplace = self.force_allocation(dest)?;
782+
let mut alloc = self.get_place_alloc_mut(&mplace)?.unwrap();
783+
alloc.write_ptr_sized(Size::ZERO, addr)?;
784+
}
785+
786+
sym::va_end => {
787+
let ptr_size = self.tcx.data_layout.pointer_size();
788+
789+
// The only argument is a `&mut VaList`.
790+
let ap_ref = self.read_pointer(&args[0])?;
791+
792+
// The first bytes of the `VaList` value store a pointer. The `AllocId` of this
793+
// pointer is a key into a global map of variable argument lists. The offset is
794+
// used as the index of the argument to read.
795+
let va_list_ptr = {
796+
let alloc = self
797+
.get_ptr_alloc(ap_ref, ptr_size)?
798+
.expect("va_list storage should not be a ZST");
799+
let scalar = alloc.read_pointer(Size::ZERO)?;
800+
scalar.to_pointer(self)?
801+
};
802+
803+
let (prov, _offset) = va_list_ptr.into_raw_parts();
804+
let alloc_id = prov.unwrap().get_alloc_id().unwrap();
805+
806+
let Some(_) = self.remove_va_list_alloc(alloc_id) else {
807+
throw_unsup_format!("va_end on unknown va_list allocation {:?}", alloc_id)
808+
};
809+
}
810+
811+
sym::va_arg => {
812+
let ptr_size = self.tcx.data_layout.pointer_size();
813+
814+
// The only argument is a `&mut VaList`.
815+
let ap_ref = self.read_pointer(&args[0])?;
816+
817+
// The first bytes of the `VaList` value store a pointer. The `AllocId` of this
818+
// pointer is a key into a global map of variable argument lists. The offset is
819+
// used as the index of the argument to read.
820+
let va_list_ptr = {
821+
let alloc = self
822+
.get_ptr_alloc(ap_ref, ptr_size)?
823+
.expect("va_list storage should not be a ZST");
824+
let scalar = alloc.read_pointer(Size::ZERO)?;
825+
scalar.to_pointer(self)?
826+
};
827+
828+
let (prov, offset) = va_list_ptr.into_raw_parts();
829+
let alloc_id = prov.unwrap().get_alloc_id().unwrap();
830+
let index = offset.bytes();
831+
832+
let Some(varargs) = self.remove_va_list_alloc(alloc_id) else {
833+
throw_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id)
834+
};
835+
836+
let Some(src_mplace) = varargs.get(offset.bytes_usize()).cloned() else {
837+
throw_ub!(VaArgOutOfBounds)
838+
};
839+
840+
// Update the offset in this `VaList` value so that a subsequent call to `va_arg`
841+
// reads the next argument.
842+
let new_va_list_ptr = self.va_list_ptr(varargs, index + 1);
843+
let addr = Scalar::from_pointer(new_va_list_ptr, self);
844+
let mut alloc = self
845+
.get_ptr_alloc_mut(ap_ref, ptr_size)?
846+
.expect("va_list storage should not be a ZST");
847+
alloc.write_ptr_sized(Size::ZERO, addr)?;
848+
849+
// NOTE: In C some type conversions are allowed (e.g. casting between signed and
850+
// unsigned integers). For now we require c-variadic arguments to be read with the
851+
// exact type they were passed as.
852+
if src_mplace.layout.ty != dest.layout.ty {
853+
throw_unsup_format!(
854+
"va_arg type mismatch: requested `{}`, but next argument is `{}`",
855+
dest.layout.ty,
856+
src_mplace.layout.ty
857+
);
858+
}
859+
860+
self.copy_op(&src_mplace, dest)?;
861+
}
862+
753863
// Unsupported intrinsic: skip the return_to_block below.
754864
_ => return interp_ok(false),
755865
}

0 commit comments

Comments
 (0)