Skip to content

Support allocation failures when interpreting MIR #86255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 4, 2021
Merged
Show file tree
Hide file tree
Changes from 14 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
5 changes: 4 additions & 1 deletion compiler/rustc_codegen_cranelift/src/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ pub(crate) fn get_vtable<'tcx>(
let vtable_ptr = if let Some(vtable_ptr) = fx.vtables.get(&(ty, trait_ref)) {
*vtable_ptr
} else {
let vtable_alloc_id = fx.tcx.vtable_allocation(ty, trait_ref);
let vtable_alloc_id = match fx.tcx.vtable_allocation(ty, trait_ref) {
Ok(alloc) => alloc,
Err(_) => fx.tcx.sess.fatal("allocation of constant vtable failed"),
};
let vtable_allocation = fx.tcx.global_alloc(vtable_alloc_id).unwrap_memory();
let vtable_ptr = pointer_for_allocation(fx, vtable_allocation);

Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_codegen_ssa/src/meth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ pub fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>(
return val;
}

let vtable_alloc_id = tcx.vtable_allocation(ty, trait_ref);
let vtable_alloc_id = match tcx.vtable_allocation(ty, trait_ref) {
Ok(alloc) => alloc,
Err(_) => tcx.sess.fatal("allocation of constant vtable failed"),
};
let vtable_allocation = tcx.global_alloc(vtable_alloc_id).unwrap_memory();
let vtable_const = cx.const_data_from_alloc(vtable_allocation);
let align = cx.data_layout().pointer_align.abi;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#![feature(associated_type_defaults)]
#![feature(iter_zip)]
#![feature(thread_local_const_init)]
#![feature(try_reserve)]
#![recursion_limit = "512"]

#[macro_use]
Expand Down
25 changes: 19 additions & 6 deletions compiler/rustc_middle/src/mir/interpret/allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ use rustc_data_structures::sorted_map::SortedMap;
use rustc_target::abi::{Align, HasDataLayout, Size};

use super::{
read_target_uint, write_target_uint, AllocId, InterpError, Pointer, Scalar, ScalarMaybeUninit,
UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo,
read_target_uint, write_target_uint, AllocId, InterpError, InterpResult, Pointer,
ResourceExhaustionInfo, Scalar, ScalarMaybeUninit, UndefinedBehaviorInfo, UninitBytesAccess,
UnsupportedOpInfo,
};

/// This type represents an Allocation in the Miri/CTFE core engine.
Expand Down Expand Up @@ -121,15 +122,27 @@ impl<Tag> Allocation<Tag> {
Allocation::from_bytes(slice, Align::ONE, Mutability::Not)
}

pub fn uninit(size: Size, align: Align) -> Self {
Allocation {
bytes: vec![0; size.bytes_usize()],
/// Try to create an Allocation of `size` bytes, failing if there is not enough memory
/// available to the compiler to do so.
pub fn uninit(size: Size, align: Align) -> InterpResult<'static, Self> {
let mut bytes = Vec::new();
bytes.try_reserve(size.bytes_usize()).map_err(|_| {
// This results in an error that can happen non-deterministically, since the memory
// available to the compiler can change between runs. Normally queries are always
// deterministic. However, we can be non-determinstic here because all uses of const
// evaluation (including ConstProp!) will make compilation fail (via hard error
// or ICE) upon encountering a `MemoryExhausted` error.
InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted)
})?;
bytes.resize(size.bytes_usize(), 0);
Ok(Allocation {
bytes,
relocations: Relocations::new(),
init_mask: InitMask::new(size, false),
align,
mutability: Mutability::Mut,
extra: (),
}
})
}
}

Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ pub enum ResourceExhaustionInfo {
///
/// The exact limit is set by the `const_eval_limit` attribute.
StepLimitReached,
/// There is not enough memory to perform an allocation.
MemoryExhausted,
}

impl fmt::Display for ResourceExhaustionInfo {
Expand All @@ -435,6 +437,9 @@ impl fmt::Display for ResourceExhaustionInfo {
StepLimitReached => {
write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)")
}
MemoryExhausted => {
write!(f, "tried to allocate more memory than available to compiler")
}
}
}
}
Expand Down Expand Up @@ -525,7 +530,8 @@ impl InterpError<'_> {
use InterpError::*;
match *self {
MachineStop(ref err) => err.is_hard_err(),
InterpError::UndefinedBehavior(_) => true,
UndefinedBehavior(_) => true,
ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted) => true,
_ => false,
}
}
Expand Down
10 changes: 5 additions & 5 deletions compiler/rustc_middle/src/ty/vtable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::convert::TryFrom;

use crate::mir::interpret::{alloc_range, AllocId, Allocation, Pointer, Scalar};
use crate::mir::interpret::{alloc_range, AllocId, Allocation, InterpResult, Pointer, Scalar};
use crate::ty::fold::TypeFoldable;
use crate::ty::{self, DefId, SubstsRef, Ty, TyCtxt};
use rustc_ast::Mutability;
Expand Down Expand Up @@ -28,11 +28,11 @@ impl<'tcx> TyCtxt<'tcx> {
self,
ty: Ty<'tcx>,
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
) -> AllocId {
) -> InterpResult<'tcx, AllocId> {
let tcx = self;
let vtables_cache = tcx.vtables_cache.lock();
if let Some(alloc_id) = vtables_cache.get(&(ty, poly_trait_ref)).cloned() {
return alloc_id;
return Ok(alloc_id);
}
drop(vtables_cache);

Expand Down Expand Up @@ -60,7 +60,7 @@ impl<'tcx> TyCtxt<'tcx> {
let ptr_align = tcx.data_layout.pointer_align.abi;

let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
let mut vtable = Allocation::uninit(vtable_size, ptr_align);
let mut vtable = Allocation::uninit(vtable_size, ptr_align)?;

// No need to do any alignment checks on the memory accesses below, because we know the
// allocation is correctly aligned as we created it above. Also we're only offsetting by
Expand Down Expand Up @@ -101,6 +101,6 @@ impl<'tcx> TyCtxt<'tcx> {
let alloc_id = tcx.create_memory_alloc(tcx.intern_const_alloc(vtable));
let mut vtables_cache = self.vtables_cache.lock();
vtables_cache.insert((ty, poly_trait_ref), alloc_id);
alloc_id
Ok(alloc_id)
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_mir/src/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn eval_body_using_ecx<'mir, 'tcx>(
);
let layout = ecx.layout_of(body.return_ty().subst(tcx, cid.instance.substs))?;
assert!(!layout.is_unsized());
let ret = ecx.allocate(layout, MemoryKind::Stack);
let ret = ecx.allocate(layout, MemoryKind::Stack)?;

let name =
with_no_trimmed_paths(|| ty::tls::with(|tcx| tcx.def_path_str(cid.instance.def_id())));
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
Size::from_bytes(size as u64),
align,
interpret::MemoryKind::Machine(MemoryKind::Heap),
);
)?;
ecx.write_scalar(Scalar::Ptr(ptr), dest)?;
}
_ => {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir/src/interpret/intern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
&MPlaceTy<'tcx, M::PointerTag>,
) -> InterpResult<'tcx, ()>,
) -> InterpResult<'tcx, &'tcx Allocation> {
let dest = self.allocate(layout, MemoryKind::Stack);
let dest = self.allocate(layout, MemoryKind::Stack)?;
f(self, &dest)?;
let ptr = dest.ptr.assert_ptr();
assert_eq!(ptr.offset, Size::ZERO);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
.type_of(self.tcx.require_lang_item(LangItem::PanicLocation, None))
.subst(*self.tcx, self.tcx.mk_substs([self.tcx.lifetimes.re_erased.into()].iter()));
let loc_layout = self.layout_of(loc_ty).unwrap();
let location = self.allocate(loc_layout, MemoryKind::CallerLocation);
// This can fail if rustc runs out of memory right here. Trying to emit an error would be
// pointless, since that would require allocating more memory than a Location.
let location = self.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();

// Initialize fields.
self.write_immediate(file.to_ref(), &self.mplace_field(&location, 0).unwrap().into())
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_mir/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
size: Size,
align: Align,
kind: MemoryKind<M::MemoryKind>,
) -> Pointer<M::PointerTag> {
let alloc = Allocation::uninit(size, align);
self.allocate_with(alloc, kind)
) -> InterpResult<'static, Pointer<M::PointerTag>> {
let alloc = Allocation::uninit(size, align)?;
Ok(self.allocate_with(alloc, kind))
}

pub fn allocate_bytes(
Expand Down Expand Up @@ -257,7 +257,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {

// For simplicities' sake, we implement reallocate as "alloc, copy, dealloc".
// This happens so rarely, the perf advantage is outweighed by the maintenance cost.
let new_ptr = self.allocate(new_size, new_align, kind);
let new_ptr = self.allocate(new_size, new_align, kind)?;
let old_size = match old_size_and_align {
Some((size, _align)) => size,
None => self.get_raw(ptr.alloc_id)?.size(),
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_mir/src/interpret/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ where
let (size, align) = self
.size_and_align_of(&meta, &local_layout)?
.expect("Cannot allocate for non-dyn-sized type");
let ptr = self.memory.allocate(size, align, MemoryKind::Stack);
let ptr = self.memory.allocate(size, align, MemoryKind::Stack)?;
let mplace = MemPlace { ptr: ptr.into(), align, meta };
if let LocalValue::Live(Operand::Immediate(value)) = local_val {
// Preserve old value.
Expand Down Expand Up @@ -1018,9 +1018,9 @@ where
&mut self,
layout: TyAndLayout<'tcx>,
kind: MemoryKind<M::MemoryKind>,
) -> MPlaceTy<'tcx, M::PointerTag> {
let ptr = self.memory.allocate(layout.size, layout.align.abi, kind);
MPlaceTy::from_aligned_ptr(ptr, layout)
) -> InterpResult<'static, MPlaceTy<'tcx, M::PointerTag>> {
let ptr = self.memory.allocate(layout.size, layout.align.abi, kind)?;
Ok(MPlaceTy::from_aligned_ptr(ptr, layout))
}

/// Returns a wide MPlace of type `&'static [mut] str` to a new 1-aligned allocation.
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir/src/interpret/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
ensure_monomorphic_enough(*self.tcx, ty)?;
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;

let vtable_allocation = self.tcx.vtable_allocation(ty, poly_trait_ref);
let vtable_allocation = self.tcx.vtable_allocation(ty, poly_trait_ref)?;

let vtable_ptr = self.memory.global_base_pointer(Pointer::from(vtable_allocation))?;

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Rust MIR: a lowered representation of Rust.
#![feature(option_get_or_insert_default)]
#![feature(once_cell)]
#![feature(control_flow_enum)]
#![feature(try_reserve)]
#![recursion_limit = "256"]

#[macro_use]
Expand Down
79 changes: 68 additions & 11 deletions compiler/rustc_mir/src/transform/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ use rustc_trait_selection::traits;
use crate::const_eval::ConstEvalErr;
use crate::interpret::{
self, compile_time_machine, AllocId, Allocation, ConstValue, CtfeValidationMode, Frame, ImmTy,
Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemPlace, Memory, MemoryKind, OpTy,
Operand as InterpOperand, PlaceTy, Pointer, Scalar, ScalarMaybeUninit, StackPopCleanup,
StackPopUnwind,
Immediate, InterpCx, InterpError, InterpResult, LocalState, LocalValue, MemPlace, Memory,
MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Pointer, ResourceExhaustionInfo, Scalar,
ScalarMaybeUninit, StackPopCleanup, StackPopUnwind,
};
use crate::transform::MirPass;

Expand Down Expand Up @@ -393,7 +393,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
.filter(|ret_layout| {
!ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
})
.map(|ret_layout| ecx.allocate(ret_layout, MemoryKind::Stack).into());
.and_then(|ret_layout| {
let alloc = ecx.allocate(ret_layout, MemoryKind::Stack);
Self::check_interpresult(tcx, &alloc);
alloc.ok()
})
.map(Into::into);

ecx.push_stack_frame(
Instance::new(def_id, substs),
Expand All @@ -416,11 +421,27 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}

/// Some `InterpError`s could be ignored but must not be to ensure that queries are stable.
fn check_interpresult<T>(tcx: TyCtxt<'tcx>, error: &InterpResult<'tcx, T>) {
if let Err(e) = error {
if matches!(
e.kind(),
InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted)
) {
// Memory errors can't be ignored since otherwise the amount of available
// memory influences the result of optimization and the build. The error
// doesn't need to be fatal since no code will actually be generated anyways.
tcx.sess.fatal("memory exhausted during optimization");
}
}
}

fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
let op = match self.ecx.eval_place_to_op(place, None) {
Ok(op) => op,
Err(e) => {
trace!("get_const failed: {}", e);
Self::check_interpresult::<()>(self.tcx, &Err(e));
return None;
}
};
Expand Down Expand Up @@ -492,7 +513,19 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
},
ConstantKind::Val(_, ty) => ty.needs_subst(),
};
if lint_only {
// Memory errors can't be ignored since otherwise the amount of available
// memory influences the result of optimization and the build. The error
// doesn't need to be fatal since no code will actually be generated anyways.
// FIXME(#86255): use err.error.is_hard_err(), but beware of backwards
// compatibility and interactions with promoteds
if lint_only
&& !matches!(
err.error,
InterpError::ResourceExhaustion(
ResourceExhaustionInfo::MemoryExhausted,
),
)
{
// Out of backwards compatibility we cannot report hard errors in unused
// generic functions using associated constants of the generic parameters.
err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span));
Expand All @@ -510,7 +543,12 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
/// Returns the value, if any, of evaluating `place`.
fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
trace!("eval_place(place={:?})", place);
self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
let tcx = self.tcx;
self.use_ecx(|this| {
let val = this.ecx.eval_place_to_op(place, None);
Self::check_interpresult(tcx, &val);
val
})
}

/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
Expand Down Expand Up @@ -571,8 +609,17 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
right: &Operand<'tcx>,
source_info: SourceInfo,
) -> Option<()> {
let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
let tcx = self.tcx;
let r = self.use_ecx(|this| {
let val = this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?);
Self::check_interpresult(tcx, &val);
val
});
let l = self.use_ecx(|this| {
let val = this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?);
Self::check_interpresult(tcx, &val);
val
});
// Check for exceeding shifts *even if* we cannot evaluate the LHS.
if op == BinOp::Shr || op == BinOp::Shl {
let r = r?;
Expand Down Expand Up @@ -738,18 +785,24 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
rvalue: &Rvalue<'tcx>,
place: Place<'tcx>,
) -> Option<()> {
let tcx = self.tcx;
self.use_ecx(|this| {
match rvalue {
Rvalue::BinaryOp(op, box (left, right))
| Rvalue::CheckedBinaryOp(op, box (left, right)) => {
let l = this.ecx.eval_operand(left, None);
let r = this.ecx.eval_operand(right, None);
Self::check_interpresult(tcx, &l);
Self::check_interpresult(tcx, &r);

let const_arg = match (l, r) {
(Ok(ref x), Err(_)) | (Err(_), Ok(ref x)) => this.ecx.read_immediate(x)?,
(Err(e), Err(_)) => return Err(e),
(Ok(_), Ok(_)) => {
this.ecx.eval_rvalue_into_place(rvalue, place)?;
Self::check_interpresult(
tcx,
&this.ecx.eval_rvalue_into_place(rvalue, place),
);
return Ok(());
}
};
Expand Down Expand Up @@ -785,12 +838,16 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}
_ => {
this.ecx.eval_rvalue_into_place(rvalue, place)?;
let res = this.ecx.eval_rvalue_into_place(rvalue, place);
Self::check_interpresult(tcx, &res);
res?
}
}
}
_ => {
this.ecx.eval_rvalue_into_place(rvalue, place)?;
let res = this.ecx.eval_rvalue_into_place(rvalue, place);
Self::check_interpresult(tcx, &res);
res?
}
}

Expand Down
Loading