Skip to content

Commit fd10f4e

Browse files
committed
implement va_arg in rustc_const_eval
1 parent 97f9c08 commit fd10f4e

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use crate::fluent_generated as fluent;
2323
use crate::interpret::{
2424
self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
2525
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar,
26-
compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub,
27-
throw_ub_custom, throw_unsup, throw_unsup_format,
26+
compile_time_machine, err_inval, err_unsup_format, interp_ok, throw_exhaust, throw_inval,
27+
throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format,
2828
};
2929

3030
/// When hitting this many interpreted terminators we emit a deny by default lint
@@ -586,6 +586,51 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
586586
}
587587
}
588588

589+
sym::va_arg => {
590+
let ptr_size = ecx.tcx.data_layout.pointer_size();
591+
592+
// The only argument is a `&mut VaList`.
593+
let ap_ref = ecx.read_pointer(&args[0])?;
594+
595+
// The first bytes of the `VaList` value store a pointer. The `AllocId` of this
596+
// pointer is a key into a global map of variable argument lists. The offset is
597+
// used as the index of the argument to read.
598+
let va_list_ptr = {
599+
let alloc = ecx
600+
.get_ptr_alloc(ap_ref, ptr_size)?
601+
.expect("va_list storage should not be a ZST");
602+
let scalar = alloc.read_pointer(Size::ZERO)?; // or: alloc.read_scalar(Size::ZERO, ptr_layout)?
603+
scalar.to_pointer(ecx)?
604+
};
605+
606+
let (prov, offset) = va_list_ptr.into_raw_parts();
607+
let alloc_id = prov.unwrap().alloc_id();
608+
let index = offset.bytes_usize();
609+
610+
// Update the offset in this `VaList` value so that a subsequent call to `va_arg`
611+
// reads the next argument.
612+
let new_va_list_ptr = va_list_ptr.wrapping_offset(Size::from_bytes(1), ecx);
613+
let addr = Scalar::from_maybe_pointer(new_va_list_ptr, ecx);
614+
let mut alloc = ecx
615+
.get_ptr_alloc_mut(ap_ref, ptr_size)?
616+
.expect("va_list storage should not be a ZST");
617+
alloc.write_ptr_sized(Size::ZERO, addr)?;
618+
619+
let arguments = ecx.memory.va_list_map.get(&alloc_id).ok_or_else(|| {
620+
err_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id)
621+
})?;
622+
623+
let src_mplace = arguments
624+
.get(index)
625+
.ok_or_else(|| err_unsup_format!("va_arg out of bounds (index={index})"))?
626+
.clone();
627+
628+
// NOTE: In C some type conversions are allowed (e.g. casting between signed and
629+
// unsigned integers). For now we require c-variadic arguments to be read with the
630+
// exact type they were passed as.
631+
ecx.copy_op(&src_mplace, dest)?;
632+
}
633+
589634
_ => {
590635
// We haven't handled the intrinsic, let's see if we can use a fallback body.
591636
if ecx.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {

compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub struct Memory<'tcx, M: Machine<'tcx>> {
128128
/// Map for "extra" function pointers.
129129
extra_fn_ptr_map: FxIndexMap<AllocId, M::ExtraFnVal>,
130130

131-
pub(super) va_list_map: FxIndexMap<AllocId, Vec<MPlaceTy<'tcx, M::Provenance>>>,
131+
pub(crate) va_list_map: FxIndexMap<AllocId, Vec<MPlaceTy<'tcx, M::Provenance>>>,
132132

133133
/// To be able to compare pointers with null, and to check alignment for accesses
134134
/// to ZSTs (where pointers may dangle), we keep track of the size even for allocations

0 commit comments

Comments
 (0)