Implement valtree

valtree is a version of constants that is inherently safe to be used within types.
This is in contrast to ty::Const which can have different representations of the same value. These representation differences can show up in hashing or equality comparisons, breaking type equality of otherwise equal types.
valtrees do not have this problem.
This commit is contained in:
Oli Scherer 2021-02-22 14:09:24 +00:00
parent 0cc64a34e9
commit a4fbac163e
10 changed files with 132 additions and 6 deletions

View File

@ -13,7 +13,7 @@
use super::{AllocId, Allocation, InterpResult, Pointer, PointerArithmetic};
/// Represents the result of const evaluation via the `eval_to_allocation` query.
#[derive(Clone, HashStable, TyEncodable, TyDecodable, Debug)]
#[derive(Copy, Clone, HashStable, TyEncodable, TyDecodable, Debug, Hash, Eq, PartialEq)]
pub struct ConstAlloc<'tcx> {
// the value lives here, at offset 0, and that allocation definitely is a `AllocKind::Memory`
// (so you can use `AllocMap::unwrap_memory`).

View File

@ -785,6 +785,14 @@
cache_on_disk_if { true }
}
/// Convert an evaluated constant to a type level constant or
/// return `None` if that is not possible.
query const_to_valtree(
key: ty::ParamEnvAnd<'tcx, ConstAlloc<'tcx>>
) -> Option<ty::ValTree> {
desc { "destructure constant" }
}
/// Destructure a constant ADT or array into its variant index and its
/// field values.
query destructure_const(

View File

@ -10,9 +10,11 @@
mod int;
mod kind;
mod valtree;
pub use int::*;
pub use kind::*;
pub use valtree::*;
/// Typed constant value.
#[derive(Copy, Clone, Debug, Hash, TyEncodable, TyDecodable, Eq, PartialEq, Ord, PartialOrd)]

View File

@ -0,0 +1,15 @@
use super::ScalarInt;
use rustc_macros::HashStable;
#[derive(Clone, Debug, Hash, TyEncodable, TyDecodable, Eq, PartialEq, Ord, PartialOrd)]
#[derive(HashStable)]
pub enum ValTree {
Leaf(ScalarInt),
Branch(Vec<ValTree>),
}
impl ValTree {
pub fn zst() -> Self {
Self::Branch(Vec::new())
}
}

View File

@ -55,7 +55,7 @@
pub use self::binding::BindingMode;
pub use self::binding::BindingMode::*;
pub use self::consts::{Const, ConstInt, ConstKind, InferConst, ScalarInt};
pub use self::consts::{Const, ConstInt, ConstKind, InferConst, ScalarInt, ValTree};
pub use self::context::{
tls, CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations,
CtxtInterners, DelaySpanBugEmitted, FreeRegionInfo, GeneratorInteriorTypeCause, GlobalCtxt,

View File

@ -14,8 +14,8 @@
use crate::middle::stability::{self, DeprecationEntry};
use crate::mir;
use crate::mir::interpret::GlobalId;
use crate::mir::interpret::{ConstAlloc, LitToConstError, LitToConstInput};
use crate::mir::interpret::{ConstValue, EvalToAllocationRawResult, EvalToConstValueResult};
use crate::mir::interpret::{LitToConstError, LitToConstInput};
use crate::mir::mono::CodegenUnit;
use crate::traits::query::{
CanonicalPredicateGoal, CanonicalProjectionGoal, CanonicalTyGoal,

View File

@ -3,12 +3,15 @@
use std::convert::TryFrom;
use rustc_hir::Mutability;
use rustc_middle::mir;
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::{
mir::{self, interpret::ConstAlloc},
ty::ScalarInt,
};
use rustc_span::{source_map::DUMMY_SP, symbol::Symbol};
use crate::interpret::{
intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, MemPlaceMeta, Scalar,
intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, MPlaceTy, MemPlaceMeta, Scalar,
};
mod error;
@ -35,6 +38,91 @@ pub(crate) fn const_caller_location(
ConstValue::Scalar(loc_place.ptr)
}
/// Convert an evaluated constant to a type level constant
pub(crate) fn const_to_valtree<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
raw: ConstAlloc<'tcx>,
) -> Option<ty::ValTree> {
let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
let place = ecx.raw_const_to_mplace(raw).unwrap();
const_to_valtree_inner(&ecx, &place)
}
fn const_to_valtree_inner<'tcx>(
ecx: &CompileTimeEvalContext<'tcx, 'tcx>,
place: &MPlaceTy<'tcx>,
) -> Option<ty::ValTree> {
let branches = |n, variant| {
let place = match variant {
Some(variant) => ecx.mplace_downcast(&place, variant).unwrap(),
None => *place,
};
let variant =
variant.map(|variant| Some(ty::ValTree::Leaf(ScalarInt::from(variant.as_u32()))));
let fields = (0..n).map(|i| {
let field = ecx.mplace_field(&place, i).unwrap();
const_to_valtree_inner(ecx, &field)
});
Some(ty::ValTree::Branch(variant.into_iter().chain(fields).collect::<Option<_>>()?))
};
match place.layout.ty.kind() {
ty::FnDef(..) => Some(ty::ValTree::zst()),
ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => {
let val = ecx.read_immediate(&place.into()).unwrap();
let val = val.to_scalar().unwrap();
Some(ty::ValTree::Leaf(val.assert_int()))
}
// Raw pointers are not allowed in type level constants, as raw pointers cannot be treated
// like references. If we looked behind the raw pointer, we may be breaking the meaning of
// the raw pointer. Equality on raw pointers is performed on the pointer and not on the pointee,
// and we cannot guarantee any kind of pointer stability in the type system.
// Technically we could allow function pointers, but they are not guaranteed to be the
// same as the function pointers at runtime.
ty::FnPtr(_) | ty::RawPtr(_) => None,
ty::Ref(..) => unimplemented!("need to use deref_const"),
ty::Dynamic(..) => unimplemented!(
"for trait objects we must look at the vtable and figure out the real type"
),
ty::Slice(_) | ty::Str => {
unimplemented!("need to find the backing data of the slice/str and recurse on that")
}
ty::Tuple(substs) => branches(substs.len(), None),
ty::Array(_, len) => branches(usize::try_from(len.eval_usize(ecx.tcx.tcx, ecx.param_env)).unwrap(), None),
ty::Adt(def, _) => {
if def.variants.is_empty() {
// Uninhabited
return None;
}
let variant = ecx.read_discriminant(&place.into()).unwrap().1;
branches(def.variants[variant].fields.len(), Some(variant))
}
ty::Never
| ty::Error(_)
| ty::Foreign(..)
| ty::Infer(ty::FreshIntTy(_))
| ty::Infer(ty::FreshFloatTy(_))
| ty::Projection(..)
| ty::Param(_)
| ty::Bound(..)
| ty::Placeholder(..)
// FIXME(oli-obk): we could look behind opaque types
| ty::Opaque(..)
| ty::Infer(_)
// FIXME(oli-obk): we can probably encode closures just like structs
| ty::Closure(..)
| ty::Generator(..)
| ty::GeneratorWitness(..) => None,
}
}
/// This function uses `unwrap` copiously, because an already validated constant
/// must have valid fields and can thus never fail outside of compiler bugs. However, it is
/// invoked from the pretty printer, where it can receive enums with no variants and e.g.

View File

@ -531,7 +531,7 @@ fn mplace_subslice(
base.offset(from_offset, meta, layout, self)
}
pub(super) fn mplace_downcast(
pub(crate) fn mplace_downcast(
&self,
base: &MPlaceTy<'tcx, M::PointerTag>,
variant: VariantIdx,

View File

@ -63,6 +63,10 @@ pub fn provide(providers: &mut Providers) {
let (param_env, value) = param_env_and_value.into_parts();
const_eval::destructure_const(tcx, param_env, value)
};
providers.const_to_valtree = |tcx, param_env_and_value| {
let (param_env, raw) = param_env_and_value.into_parts();
const_eval::const_to_valtree(tcx, param_env, raw)
};
providers.deref_const = |tcx, param_env_and_value| {
let (param_env, value) = param_env_and_value.into_parts();
const_eval::deref_const(tcx, param_env, value)

View File

@ -228,6 +228,15 @@ fn default_span(&self, _: TyCtxt<'_>) -> Span {
}
}
impl<'tcx> Key for mir::interpret::ConstAlloc<'tcx> {
fn query_crate(&self) -> CrateNum {
LOCAL_CRATE
}
fn default_span(&self, _: TyCtxt<'_>) -> Span {
DUMMY_SP
}
}
impl<'tcx> Key for ty::PolyTraitRef<'tcx> {
fn query_crate(&self) -> CrateNum {
self.def_id().krate