Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 0 additions & 9 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,15 +697,6 @@ 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 Some(coroutine_kind) = sig.header.coroutine_kind {
self.dcx().emit_err(errors::CoroutineAndCVariadic {
spans: vec![coroutine_kind.span(), variadic_param.span],
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
4 changes: 4 additions & 0 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,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 @@ -430,6 +432,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/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ 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,
Expand All @@ -509,6 +510,7 @@ 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,
}
Expand All @@ -535,6 +537,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
| InvalidMeta(InvalidMetaKind::TooBig)
| InvalidUninitBytes(None)
| DeadLocal
| VaArgOutOfBounds
| UninhabitedEnumVariantWritten(_)
| UninhabitedEnumVariantRead(_) => {}

Expand Down Expand Up @@ -609,6 +612,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
WriteToReadOnly(alloc)
| DerefFunctionPointer(alloc)
| DerefVTablePointer(alloc)
| DerefVaListPointer(alloc)
| DerefTypeIdPointer(alloc) => {
diag.arg("allocation", alloc);
}
Expand Down
73 changes: 61 additions & 12 deletions compiler/rustc_const_eval/src/interpret/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::borrow::Cow;

use either::{Left, Right};
use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx};
use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, VariantIdx};
use rustc_data_structures::assert_matches;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::layout::{IntegerExt, TyAndLayout};
Expand All @@ -15,9 +15,9 @@ use tracing::field::Empty;
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,
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy,
PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo,
interp_ok, throw_ub, throw_ub_custom,
};
use crate::interpret::EnteredTraceSpan;
use crate::{enter_trace_span, fluent_generated as fluent};
Expand Down Expand Up @@ -349,12 +349,22 @@ 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())?;
let (fixed_count, c_variadic_args) = if caller_fn_abi.c_variadic {
let sig = self.tcx.fn_sig(instance.def_id()).skip_binder();
let fixed_count = sig.inputs().skip_binder().len();
assert!(caller_fn_abi.args.len() >= fixed_count);
let extra_tys: Vec<Ty<'tcx>> =
caller_fn_abi.args[fixed_count..].iter().map(|arg_abi| arg_abi.layout.ty).collect();

if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
throw_unsup_format!("calling a c-variadic function is not supported");
(fixed_count, self.tcx.mk_type_list(&extra_tys))
} else {
(caller_fn_abi.args.len(), ty::List::empty())
};

let callee_fn_abi = self.fn_abi_of_instance(instance, c_variadic_args)?;

if callee_fn_abi.c_variadic ^ caller_fn_abi.c_variadic {
unreachable!("caller and callee disagree on being c-variadic");
}

if caller_fn_abi.conv != callee_fn_abi.conv {
Expand Down Expand Up @@ -436,16 +446,55 @@ 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 {
callee_fn_abi.args[..fixed_count].iter().enumerate()
} else {
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() {
// The callee's signature has an additional VaList argument, that the caller
// won't actually pass. Here we synthesize a `VaList` value, whose leading bytes
// are a pointer that can be mapped to the corresponding variable argument list.
self.storage_live(local)?;

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

// Consume the remaining arguments and store them in a global allocation.
let mut varargs = Vec::new();
for (fn_arg, abi) in &mut caller_args {
let op = self.copy_fn_arg(fn_arg);
let mplace = self.allocate(abi.layout, MemoryKind::Stack)?;
self.copy_op(&op, &mplace)?;

varargs.push(mplace);
}

// When the frame is dropped, this ID is used to deallocate the variable arguments list.
self.frame_mut().va_list = varargs.clone();

// This is a new VaList, so start at index 0.
let ptr = self.va_list_ptr(varargs, 0);
let addr = Scalar::from_pointer(ptr, self);

// Store the pointer to the global variable arguments list allocation in the
// first bytes of the `VaList` value.
let mut alloc = self
.get_ptr_alloc_mut(mplace.ptr(), self.data_layout().pointer_size())?
.expect("not a ZST");

alloc.write_ptr_sized(Size::ZERO, addr)?;
} 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
112 changes: 111 additions & 1 deletion compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use super::util::ensure_monomorphic_enough;
use super::{
AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer,
PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval,
throw_ub_custom, throw_ub_format,
throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format,
};
use crate::fluent_generated as fluent;
use crate::interpret::Writeable;
Expand Down Expand Up @@ -734,6 +734,116 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.float_muladd_intrinsic::<Quad>(args, dest, MulAddType::Nondeterministic)?
}

sym::va_copy => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this code looks kind of rough/unpolished, I'm probably just missing some convenient helpers.

// fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f>
let src_ptr = self.read_pointer(&args[0])?;

// Read the token pointer from the src VaList (alloc_id + offset-as-index).
let src_va_list_ptr = {
let pointer_size = tcx.data_layout.pointer_size();
let alloc = self
.get_ptr_alloc(src_ptr, pointer_size)?
.expect("va_list storage should not be a ZST");
let scalar = alloc.read_pointer(Size::ZERO)?;
scalar.to_pointer(self)?
};

let (prov, offset) = src_va_list_ptr.into_raw_parts();
let src_alloc_id = prov.unwrap().get_alloc_id().unwrap();
let index = offset.bytes();

// Look up arguments without consuming src.
let Some(arguments) = self.get_va_list_alloc(src_alloc_id) else {
throw_unsup_format!("va_copy on unknown va_list allocation {:?}", src_alloc_id);
};

// Create a new allocation pointing at the same index.
let new_va_list_ptr = self.va_list_ptr(arguments.to_vec(), index);
let addr = Scalar::from_pointer(new_va_list_ptr, self);

// Now overwrite the token pointer stored inside the VaList.
let mplace = self.force_allocation(dest)?;
let mut alloc = self.get_place_alloc_mut(&mplace)?.unwrap();
alloc.write_ptr_sized(Size::ZERO, addr)?;
}

sym::va_end => {
let ptr_size = self.tcx.data_layout.pointer_size();

// The only argument is a `&mut VaList`.
let ap_ref = self.read_pointer(&args[0])?;

// The first bytes of the `VaList` value store a pointer. The `AllocId` of this
// pointer is a key into a global map of variable argument lists. The offset is
// used as the index of the argument to read.
let va_list_ptr = {
let alloc = self
.get_ptr_alloc(ap_ref, ptr_size)?
.expect("va_list storage should not be a ZST");
let scalar = alloc.read_pointer(Size::ZERO)?;
scalar.to_pointer(self)?
};

let (prov, _offset) = va_list_ptr.into_raw_parts();
let alloc_id = prov.unwrap().get_alloc_id().unwrap();

let Some(_) = self.remove_va_list_alloc(alloc_id) else {
throw_unsup_format!("va_end on unknown va_list allocation {:?}", alloc_id)
};
}

sym::va_arg => {
let ptr_size = self.tcx.data_layout.pointer_size();

// The only argument is a `&mut VaList`.
let ap_ref = self.read_pointer(&args[0])?;

// The first bytes of the `VaList` value store a pointer. The `AllocId` of this
// pointer is a key into a global map of variable argument lists. The offset is
// used as the index of the argument to read.
let va_list_ptr = {
let alloc = self
.get_ptr_alloc(ap_ref, ptr_size)?
.expect("va_list storage should not be a ZST");
let scalar = alloc.read_pointer(Size::ZERO)?;
scalar.to_pointer(self)?
};

let (prov, offset) = va_list_ptr.into_raw_parts();
let alloc_id = prov.unwrap().get_alloc_id().unwrap();
let index = offset.bytes();

let Some(varargs) = self.remove_va_list_alloc(alloc_id) else {
throw_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id)
};

let Some(src_mplace) = varargs.get(offset.bytes_usize()).cloned() else {
throw_ub!(VaArgOutOfBounds)
};

// Update the offset in this `VaList` value so that a subsequent call to `va_arg`
// reads the next argument.
let new_va_list_ptr = self.va_list_ptr(varargs, index + 1);
let addr = Scalar::from_pointer(new_va_list_ptr, self);
let mut alloc = self
.get_ptr_alloc_mut(ap_ref, ptr_size)?
.expect("va_list storage should not be a ZST");
alloc.write_ptr_sized(Size::ZERO, addr)?;

// NOTE: In C some type conversions are allowed (e.g. casting between signed and
// unsigned integers). For now we require c-variadic arguments to be read with the
// exact type they were passed as.
if src_mplace.layout.ty != dest.layout.ty {
throw_unsup_format!(
"va_arg type mismatch: requested `{}`, but next argument is `{}`",
dest.layout.ty,
src_mplace.layout.ty
);
}

self.copy_op(&src_mplace, dest)?;
}

// Unsupported intrinsic: skip the return_to_block below.
_ => return interp_ok(false),
}
Expand Down
Loading
Loading