-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Use valtrees as the type-system representation for constant values #96591
Changes from all commits
705d818
5c95a3d
dbef6e4
17323e0
0a6815a
773d8b2
90c4b94
8093db6
060acc9
e14b34c
15c1c06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,11 @@ | ||
// Not in interpret to make sure we do not use private implementation details | ||
|
||
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}; | ||
use rustc_target::abi::VariantIdx; | ||
|
||
use crate::interpret::{ | ||
intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MemPlaceMeta, | ||
|
@@ -40,7 +39,7 @@ pub(crate) fn const_caller_location( | |
} | ||
|
||
// We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes. | ||
const VALTREE_MAX_NODES: usize = 1000; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are you removing that? It still seems desirable to deal with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm not sure about this. It's kind of trial and error to find a good max value for this, if I remember correctly I was encountering constants with >100k nodes after the normalization changes. Using some arbitrary threshold might also break crates. On tests that use constant arrays with
Really not sure what the best solution here is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
not on stable, on stable all valtrees have a size of 1 because only scalars are allowed. Getting a weird error is preferable to getting an ICE in these cases |
||
const VALTREE_MAX_NODES: usize = 100000; | ||
|
||
pub(crate) enum ValTreeCreationError { | ||
NodesOverflow, | ||
|
@@ -56,6 +55,8 @@ pub(crate) fn eval_to_valtree<'tcx>( | |
cid: GlobalId<'tcx>, | ||
) -> EvalToValTreeResult<'tcx> { | ||
let const_alloc = tcx.eval_to_allocation_raw(param_env.and(cid))?; | ||
|
||
// FIXME Need to provide a span to `eval_to_valtree` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would that span ever be used somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the rationale was to point to the use of a constant that had a Node overflow. |
||
let ecx = mk_eval_cx( | ||
tcx, DUMMY_SP, param_env, | ||
// It is absolutely crucial for soundness that | ||
|
@@ -90,40 +91,81 @@ pub(crate) fn eval_to_valtree<'tcx>( | |
} | ||
} | ||
|
||
/// 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. | ||
/// Tries to destructure constants of type Array or Adt into the constants | ||
/// of its fields. | ||
pub(crate) fn try_destructure_const<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
val: ty::Const<'tcx>, | ||
) -> InterpResult<'tcx, mir::DestructuredConst<'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), | ||
// Checks if we have any variants, to avoid downcasting to a non-existing variant (when | ||
// there are no variants `read_discriminant` successfully returns a non-existing variant | ||
// index). | ||
ty::Adt(def, _) if def.variants().is_empty() => throw_ub!(Unreachable), | ||
ty::Adt(def, _) => { | ||
let variant = ecx.read_discriminant(&op)?.1; | ||
let down = ecx.operand_downcast(&op, variant)?; | ||
(def.variant(variant).fields.len(), Some(variant), down) | ||
} | ||
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)?; | ||
let val = op_to_const(&ecx, &field_op); | ||
Ok(ty::Const::from_value(tcx, val, field_op.layout.ty)) | ||
}) | ||
.collect::<InterpResult<'tcx, Vec<_>>>()?; | ||
let fields = tcx.arena.alloc_from_iter(fields); | ||
Ok(mir::DestructuredConst { variant, fields }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a bunch of interpreter functions that are no longer being called here, nice. :) I wonder if this means that we can make some of them private to the interpreter? |
||
const_: ty::Const<'tcx>, | ||
) -> Option<ty::DestructuredConst<'tcx>> { | ||
if let ty::ConstKind::Value(valtree) = const_.kind() { | ||
let branches = match valtree { | ||
ty::ValTree::Branch(b) => b, | ||
_ => return None, | ||
}; | ||
|
||
let (fields, variant) = match const_.ty().kind() { | ||
ty::Array(inner_ty, _) | ty::Slice(inner_ty) => { | ||
// construct the consts for the elements of the array/slice | ||
let field_consts = branches | ||
.iter() | ||
.map(|b| { | ||
tcx.mk_const(ty::ConstS { kind: ty::ConstKind::Value(*b), ty: *inner_ty }) | ||
}) | ||
.collect::<Vec<_>>(); | ||
debug!(?field_consts); | ||
|
||
(field_consts, None) | ||
} | ||
ty::Adt(def, _) if def.variants().is_empty() => bug!("unreachable"), | ||
ty::Adt(def, substs) => { | ||
let variant_idx = if def.is_enum() { | ||
VariantIdx::from_u32(branches[0].unwrap_leaf().try_to_u32().ok()?) | ||
} else { | ||
VariantIdx::from_u32(0) | ||
}; | ||
let fields = &def.variant(variant_idx).fields; | ||
let mut field_consts = Vec::with_capacity(fields.len()); | ||
|
||
// Note: First element inValTree corresponds to variant of enum | ||
let mut valtree_idx = if def.is_enum() { 1 } else { 0 }; | ||
for field in fields { | ||
let field_ty = field.ty(tcx, substs); | ||
let field_valtree = branches[valtree_idx]; // first element of branches is variant | ||
let field_const = tcx.mk_const(ty::ConstS { | ||
kind: ty::ConstKind::Value(field_valtree), | ||
ty: field_ty, | ||
}); | ||
field_consts.push(field_const); | ||
valtree_idx += 1; | ||
} | ||
debug!(?field_consts); | ||
|
||
(field_consts, Some(variant_idx)) | ||
} | ||
ty::Tuple(elem_tys) => { | ||
let fields = elem_tys | ||
.iter() | ||
.enumerate() | ||
.map(|(i, elem_ty)| { | ||
let elem_valtree = branches[i]; | ||
tcx.mk_const(ty::ConstS { | ||
kind: ty::ConstKind::Value(elem_valtree), | ||
ty: elem_ty, | ||
}) | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
(fields, None) | ||
} | ||
_ => bug!("cannot destructure constant {:?}", const_), | ||
}; | ||
|
||
let fields = tcx.arena.alloc_from_iter(fields.into_iter()); | ||
|
||
Some(ty::DestructuredConst { variant, fields }) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
#[instrument(skip(tcx), level = "debug")] | ||
|
@@ -143,8 +185,8 @@ pub(crate) fn try_destructure_mir_constant<'tcx>( | |
throw_ub!(Unreachable) | ||
} | ||
ty::Adt(def, _) => { | ||
let variant = ecx.read_discriminant(&op).unwrap().1; | ||
let down = ecx.operand_downcast(&op, variant).unwrap(); | ||
let variant = ecx.read_discriminant(&op)?.1; | ||
let down = ecx.operand_downcast(&op, variant)?; | ||
(def.variants()[variant].fields.len(), Some(variant), down) | ||
} | ||
ty::Tuple(substs) => (substs.len(), None, op), | ||
|
@@ -163,43 +205,6 @@ pub(crate) fn try_destructure_mir_constant<'tcx>( | |
Ok(mir::DestructuredMirConstant { variant, fields }) | ||
} | ||
|
||
#[instrument(skip(tcx), level = "debug")] | ||
pub(crate) fn deref_const<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
val: ty::Const<'tcx>, | ||
) -> ty::Const<'tcx> { | ||
trace!("deref_const: {:?}", val); | ||
let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); | ||
let op = ecx.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().inner().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 | ||
), | ||
}, | ||
}; | ||
|
||
tcx.mk_const(ty::ConstS { kind: 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>, | ||
|
@@ -213,14 +218,14 @@ pub(crate) fn deref_mir_constant<'tcx>( | |
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 \ | ||
"deref_mir_constant 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), | ||
MemPlaceMeta::Poison => bug!("poison metadata in `deref_mir_constant`: {:#?}", 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`"), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me like we should really fix the
StableHashResult
impl foru64
instead 😅 can you do that in a separate PR or here, depending on what's easier?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do that in a separate PR.