Skip to content

Transition to valtrees pt1 #97019

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 1 commit into from
May 18, 2022
Merged
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
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pub(super) fn op_to_const<'tcx>(
} else {
// It is guaranteed that any non-slice scalar pair is actually ByRef here.
// When we come back from raw const eval, we are always by-ref. The only way our op here is
// by-val is if we are in destructure_const, i.e., if this is (a field of) something that we
// by-val is if we are in destructure_mir_constant, i.e., if this is (a field of) something that we
// "tried to make immediate" before. We wouldn't do that for non-slice scalar pairs or
// structs containing such.
op.try_as_mplace()
Expand Down
130 changes: 126 additions & 4 deletions compiler/rustc_const_eval/src/const_eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::convert::TryFrom;

use rustc_hir::Mutability;
use rustc_middle::mir;
use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::{source_map::DUMMY_SP, symbol::Symbol};

Expand All @@ -22,7 +23,7 @@ pub use error::*;
pub use eval_queries::*;
pub use fn_queries::*;
pub use machine::*;
pub(crate) use valtrees::{const_to_valtree, valtree_to_const_value};
pub(crate) use valtrees::{const_to_valtree_inner, valtree_to_const_value};

pub(crate) fn const_caller_location(
tcx: TyCtxt<'_>,
Expand All @@ -38,6 +39,57 @@ pub(crate) fn const_caller_location(
ConstValue::Scalar(Scalar::from_maybe_pointer(loc_place.ptr, &tcx))
}

// We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes.
const VALTREE_MAX_NODES: usize = 1000;

pub(crate) enum ValTreeCreationError {
NodesOverflow,
NonSupportedType,
Other,
}
pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError>;

/// Evaluates a constant and turns it into a type-level constant value.
pub(crate) fn eval_to_valtree<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
cid: GlobalId<'tcx>,
) -> EvalToValTreeResult<'tcx> {
let const_alloc = tcx.eval_to_allocation_raw(param_env.and(cid))?;
let ecx = mk_eval_cx(
tcx, DUMMY_SP, param_env,
// It is absolutely crucial for soundness that
// we do not read from static items or other mutable memory.
false,
);
let place = ecx.raw_const_to_mplace(const_alloc).unwrap();
debug!(?place);

let mut num_nodes = 0;
let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes);

match valtree_result {
Ok(valtree) => Ok(Some(valtree)),
Err(err) => {
let did = cid.instance.def_id();
let s = cid.display(tcx);
match err {
ValTreeCreationError::NodesOverflow => {
let msg = format!("maximum number of nodes exceeded in constant {}", &s);
let mut diag = match tcx.hir().span_if_local(did) {
Some(span) => tcx.sess.struct_span_err(span, &msg),
None => tcx.sess.struct_err(&msg),
};
diag.emit();

Ok(None)
}
ValTreeCreationError::NonSupportedType | ValTreeCreationError::Other => Ok(None),
}
}
}
}

/// This function should never fail for validated constants. However, it is also invoked from the
/// pretty printer which might attempt to format invalid constants and in that case it might fail.
pub(crate) fn try_destructure_const<'tcx>(
Expand All @@ -48,7 +100,6 @@ pub(crate) fn try_destructure_const<'tcx>(
trace!("destructure_const: {:?}", val);
let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
let op = ecx.const_to_op(val, None)?;

// We go to `usize` as we cannot allocate anything bigger anyway.
let (field_count, variant, down) = match val.ty().kind() {
ty::Array(_, len) => (usize::try_from(len.eval_usize(tcx, param_env)).unwrap(), None, op),
Expand All @@ -64,7 +115,6 @@ pub(crate) fn try_destructure_const<'tcx>(
ty::Tuple(substs) => (substs.len(), None, op),
_ => bug!("cannot destructure constant {:?}", val),
};

let fields = (0..field_count)
.map(|i| {
let field_op = ecx.operand_field(&down, i)?;
Expand All @@ -73,10 +123,46 @@ pub(crate) fn try_destructure_const<'tcx>(
})
.collect::<InterpResult<'tcx, Vec<_>>>()?;
let fields = tcx.arena.alloc_from_iter(fields);

Ok(mir::DestructuredConst { variant, fields })
}

#[instrument(skip(tcx), level = "debug")]
pub(crate) fn try_destructure_mir_constant<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
val: mir::ConstantKind<'tcx>,
) -> InterpResult<'tcx, mir::DestructuredMirConstant<'tcx>> {
trace!("destructure_mir_constant: {:?}", val);
let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
let op = ecx.mir_const_to_op(&val, None)?;

// We go to `usize` as we cannot allocate anything bigger anyway.
let (field_count, variant, down) = match val.ty().kind() {
ty::Array(_, len) => (len.eval_usize(tcx, param_env) as usize, None, op),
ty::Adt(def, _) if def.variants().is_empty() => {
throw_ub!(Unreachable)
}
ty::Adt(def, _) => {
let variant = ecx.read_discriminant(&op).unwrap().1;
let down = ecx.operand_downcast(&op, variant).unwrap();
(def.variants()[variant].fields.len(), Some(variant), down)
}
ty::Tuple(substs) => (substs.len(), None, op),
_ => bug!("cannot destructure mir constant {:?}", val),
};

let fields_iter = (0..field_count)
.map(|i| {
let field_op = ecx.operand_field(&down, i)?;
let val = op_to_const(&ecx, &field_op);
Ok(mir::ConstantKind::Val(val, field_op.layout.ty))
})
.collect::<InterpResult<'tcx, Vec<_>>>()?;
let fields = tcx.arena.alloc_from_iter(fields_iter);

Ok(mir::DestructuredMirConstant { variant, fields })
}

#[instrument(skip(tcx), level = "debug")]
pub(crate) fn deref_const<'tcx>(
tcx: TyCtxt<'tcx>,
Expand Down Expand Up @@ -113,3 +199,39 @@ pub(crate) fn deref_const<'tcx>(

tcx.mk_const(ty::ConstS { val: ty::ConstKind::Value(op_to_const(&ecx, &mplace.into())), ty })
}

#[instrument(skip(tcx), level = "debug")]
pub(crate) fn deref_mir_constant<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
val: mir::ConstantKind<'tcx>,
) -> mir::ConstantKind<'tcx> {
let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
let op = ecx.mir_const_to_op(&val, None).unwrap();
let mplace = ecx.deref_operand(&op).unwrap();
if let Some(alloc_id) = mplace.ptr.provenance {
assert_eq!(
tcx.get_global_alloc(alloc_id).unwrap().unwrap_memory().0.0.mutability,
Mutability::Not,
"deref_const cannot be used with mutable allocations as \
that could allow pattern matching to observe mutable statics",
);
}

let ty = match mplace.meta {
MemPlaceMeta::None => mplace.layout.ty,
MemPlaceMeta::Poison => bug!("poison metadata in `deref_const`: {:#?}", mplace),
// In case of unsized types, figure out the real type behind.
MemPlaceMeta::Meta(scalar) => match mplace.layout.ty.kind() {
ty::Str => bug!("there's no sized equivalent of a `str`"),
ty::Slice(elem_ty) => tcx.mk_array(*elem_ty, scalar.to_machine_usize(&tcx).unwrap()),
_ => bug!(
"type {} should not have metadata, but had {:?}",
mplace.layout.ty,
mplace.meta
),
},
};

mir::ConstantKind::Val(op_to_const(&ecx, &mplace.into()), ty)
}
Loading