Skip to content

Commit f5bf335

Browse files
committed
move va_list operations into InterpCx
1 parent ca3d1a3 commit f5bf335

18 files changed

Lines changed: 576 additions & 282 deletions

File tree

compiler/rustc_const_eval/src/errors.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
763763
InvalidChar(_) => msg!("interpreting an invalid 32-bit value as a char: 0x{$value}"),
764764
InvalidTag(_) => msg!("enum value has invalid tag: {$tag}"),
765765
InvalidFunctionPointer(_) => msg!("using {$pointer} as function pointer but it does not point to a function"),
766+
InvalidVaListPointer(_) => msg!("using {$pointer} as variable argument list pointer but it does not point to a variable argument list"),
766767
InvalidVTablePointer(_) => msg!("using {$pointer} as vtable pointer but it does not point to a vtable"),
767768
InvalidVTableTrait { .. } => msg!("using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected"),
768769
InvalidStr(_) => msg!("this string is not valid UTF-8: {$err}"),
@@ -778,6 +779,8 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
778779
AbiMismatchArgument { .. } => msg!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"),
779780
AbiMismatchReturn { .. } => msg!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"),
780781
VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(),
782+
CVariadicMismatch { ..} => "calling a function where the caller and callee disagree on whether the function is C-variadic".into(),
783+
CVariadicFixedCountMismatch { .. } => msg!("calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee}"),
781784
}
782785
}
783786

@@ -823,7 +826,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
823826
diag.arg("len", len);
824827
diag.arg("index", index);
825828
}
826-
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
829+
UnterminatedCString(ptr)
830+
| InvalidFunctionPointer(ptr)
831+
| InvalidVaListPointer(ptr)
832+
| InvalidVTablePointer(ptr) => {
827833
diag.arg("pointer", ptr);
828834
}
829835
InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => {
@@ -914,6 +920,14 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
914920
diag.arg("caller_ty", caller_ty);
915921
diag.arg("callee_ty", callee_ty);
916922
}
923+
CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => {
924+
diag.arg("caller_is_c_variadic", caller_is_c_variadic);
925+
diag.arg("callee_is_c_variadic", callee_is_c_variadic);
926+
}
927+
CVariadicFixedCountMismatch { caller, callee } => {
928+
diag.arg("caller", caller);
929+
diag.arg("callee", callee);
930+
}
917931
}
918932
}
919933
}

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 46 additions & 54 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, HasDataLayout, Integer, Size, VariantIdx};
7+
use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, 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, MemoryKind, OpTy,
21-
PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo,
22-
interp_ok, throw_ub, throw_ub_custom,
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,
2323
};
2424
use crate::enter_trace_span;
2525
use crate::interpret::EnteredTraceSpan;
@@ -354,23 +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-
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();
363-
364-
(fixed_count, self.tcx.mk_type_list(&extra_tys))
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)
365365
} else {
366-
(caller_fn_abi.args.len(), ty::List::empty())
366+
ty::List::empty()
367367
};
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");
373-
}
368+
let callee_fn_abi = self.fn_abi_of_instance(instance, extra_tys)?;
374369

375370
if caller_fn_abi.conv != callee_fn_abi.conv {
376371
throw_ub_custom!(
@@ -382,6 +377,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
382377
)
383378
}
384379

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+
385393
// Check that all target features required by the callee (i.e., from
386394
// the attribute `#[target_feature(enable = ...)]`) are enabled at
387395
// compile time.
@@ -453,59 +461,43 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
453461
// this is a single iterator (that handles `spread_arg`), then
454462
// `pass_argument` would be the loop body. It takes care to
455463
// not advance `caller_iter` for ignored arguments.
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() {
464+
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));
469+
for local in body.args_iter() {
464470
// Construct the destination place for this argument. At this point all
465471
// locals are still dead, so we cannot construct a `PlaceTy`.
466472
let dest = mir::Place::from(local);
467473
// `layout_of_local` does more than just the instantiation we need to get the
468474
// type, but the result gets cached so this avoids calling the instantiation
469475
// query *again* the next time this local is accessed.
470476
let ty = self.layout_of_local(self.frame(), local, None)?.ty;
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.
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.
475480
self.storage_live(local)?;
476481

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

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.
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.
491489
self.frame_mut().va_list = varargs.clone();
490+
let key = self.va_list_ptr(varargs.into());
492491

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.
492+
// Zero the VaList, so it is fully initialized.
498493
self.write_bytes_ptr(
499494
mplace.ptr(),
500495
(0..mplace.layout.size.bytes()).map(|_| 0u8),
501496
)?;
502497

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)?;
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)?;
509501
} else if Some(local) == body.spread_arg {
510502
// Make the local live once, then fill in the value field by field.
511503
self.storage_live(local)?;
@@ -545,7 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
545537
if instance.def.requires_caller_location(*self.tcx) {
546538
callee_args_abis.next().unwrap();
547539
}
548-
// 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.
549541
assert!(
550542
callee_args_abis.next().is_none(),
551543
"mismatch between callee ABI and callee body arguments"

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 50 additions & 87 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, throw_ub_custom, throw_ub_format, throw_unsup_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

@@ -751,113 +751,54 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
751751
}
752752

753753
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();
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)?;
770757

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-
};
758+
let varargs = self.get_ptr_va_list(key)?;
759+
let copy_key = self.va_list_ptr(varargs.clone());
775760

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)?;
761+
let copy_key_mplace = self.va_list_key_field(dest)?;
762+
self.write_pointer(copy_key, &copy_key_mplace)?;
784763
}
785764

786765
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-
};
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)?;
802769

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-
};
770+
self.deallocate_va_list(key)?;
809771
}
810772

811773
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-
};
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)?;
827777

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-
};
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)?;
835781

836-
let Some(src_mplace) = varargs.get(offset.bytes_usize()).cloned() else {
837-
throw_ub!(VaArgOutOfBounds)
782+
let Some(arg_mplace) = varargs.pop_front() else {
783+
throw_ub!(VaArgOutOfBounds);
838784
};
839785

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-
849786
// NOTE: In C some type conversions are allowed (e.g. casting between signed and
850787
// unsigned integers). For now we require c-variadic arguments to be read with the
851788
// exact type they were passed as.
852-
if src_mplace.layout.ty != dest.layout.ty {
789+
if arg_mplace.layout.ty != dest.layout.ty {
853790
throw_unsup_format!(
854791
"va_arg type mismatch: requested `{}`, but next argument is `{}`",
855792
dest.layout.ty,
856-
src_mplace.layout.ty
793+
arg_mplace.layout.ty
857794
);
858795
}
796+
// Copy the argument.
797+
self.copy_op(&arg_mplace, dest)?;
859798

860-
self.copy_op(&src_mplace, dest)?;
799+
// Update the VaList pointer.
800+
let new_key = self.va_list_ptr(varargs);
801+
self.write_pointer(new_key, &key_mplace)?;
861802
}
862803

863804
// Unsupported intrinsic: skip the return_to_block below.
@@ -1340,4 +1281,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
13401281
interp_ok(Some(ImmTy::from_scalar(val, cast_to)))
13411282
}
13421283
}
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+
}
13431306
}

0 commit comments

Comments
 (0)