1
Fork 0

implement valtrees as the type-system representation for constant values

This commit is contained in:
b-naber 2022-02-16 10:56:01 +01:00
parent edab34ab2a
commit 705d818bd5
116 changed files with 1606 additions and 1032 deletions

View file

@ -3,7 +3,9 @@
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/mir/index.html
use crate::mir::coverage::{CodeRegion, CoverageKind};
use crate::mir::interpret::{ConstAllocation, ConstValue, GlobalAlloc, LitToConstInput, Scalar};
use crate::mir::interpret::{
AllocRange, ConstAllocation, ConstValue, GlobalAlloc, LitToConstInput, Scalar,
};
use crate::mir::visit::MirVisitable;
use crate::ty::adjustment::PointerCast;
use crate::ty::codec::{TyDecoder, TyEncoder};
@ -1444,7 +1446,11 @@ impl<'tcx> BasicBlockData<'tcx> {
}
pub fn visitable(&self, index: usize) -> &dyn MirVisitable<'tcx> {
if index < self.statements.len() { &self.statements[index] } else { &self.terminator }
if index < self.statements.len() {
&self.statements[index]
} else {
&self.terminator
}
}
}
@ -2465,7 +2471,11 @@ impl<'tcx> Operand<'tcx> {
/// find as the `func` in a [`TerminatorKind::Call`].
pub fn const_fn_def(&self) -> Option<(DefId, SubstsRef<'tcx>)> {
let const_ty = self.constant()?.literal.ty();
if let ty::FnDef(def_id, substs) = *const_ty.kind() { Some((def_id, substs)) } else { None }
if let ty::FnDef(def_id, substs) = *const_ty.kind() {
Some((def_id, substs))
} else {
None
}
}
}
@ -2953,22 +2963,9 @@ impl<'tcx> Constant<'tcx> {
}
}
impl<'tcx> From<ty::Const<'tcx>> for ConstantKind<'tcx> {
#[inline]
fn from(ct: ty::Const<'tcx>) -> Self {
match ct.kind() {
ty::ConstKind::Value(cv) => {
// FIXME Once valtrees are introduced we need to convert those
// into `ConstValue` instances here
Self::Val(cv, ct.ty())
}
_ => Self::Ty(ct),
}
}
}
impl<'tcx> ConstantKind<'tcx> {
/// Returns `None` if the constant is not trivially safe for use in the type system.
#[inline]
pub fn const_for_ty(&self) -> Option<ty::Const<'tcx>> {
match self {
ConstantKind::Ty(c) => Some(*c),
@ -2976,6 +2973,7 @@ impl<'tcx> ConstantKind<'tcx> {
}
}
#[inline(always)]
pub fn ty(&self) -> Ty<'tcx> {
match self {
ConstantKind::Ty(c) => c.ty(),
@ -2983,10 +2981,10 @@ impl<'tcx> ConstantKind<'tcx> {
}
}
pub fn try_val(&self) -> Option<ConstValue<'tcx>> {
pub fn try_val(&self, tcx: TyCtxt<'tcx>) -> Option<ConstValue<'tcx>> {
match self {
ConstantKind::Ty(c) => match c.kind() {
ty::ConstKind::Value(v) => Some(v),
ty::ConstKind::Value(v) => Some(tcx.valtree_to_const_val((c.ty(), v))),
_ => None,
},
ConstantKind::Val(v, _) => Some(*v),
@ -2994,21 +2992,33 @@ impl<'tcx> ConstantKind<'tcx> {
}
#[inline]
pub fn try_to_value(self) -> Option<interpret::ConstValue<'tcx>> {
pub fn try_to_value(self, tcx: TyCtxt<'tcx>) -> Option<interpret::ConstValue<'tcx>> {
match self {
ConstantKind::Ty(c) => c.kind().try_to_value(),
ConstantKind::Ty(c) => match c.kind() {
ty::ConstKind::Value(valtree) => Some(tcx.valtree_to_const_val((c.ty(), valtree))),
_ => None,
},
ConstantKind::Val(val, _) => Some(val),
}
}
#[inline]
pub fn try_to_scalar(self) -> Option<Scalar> {
self.try_to_value()?.try_to_scalar()
match self {
ConstantKind::Ty(c) => match c.val() {
ty::ConstKind::Value(valtree) => match valtree {
ty::ValTree::Leaf(scalar_int) => Some(Scalar::Int(scalar_int)),
ty::ValTree::Branch(_) => None,
},
_ => None,
},
ConstantKind::Val(val, _) => val.try_to_scalar(),
}
}
#[inline]
pub fn try_to_scalar_int(self) -> Option<ScalarInt> {
Some(self.try_to_value()?.try_to_scalar()?.assert_int())
Some(self.try_to_scalar()?.assert_int())
}
#[inline]
@ -3025,9 +3035,7 @@ impl<'tcx> ConstantKind<'tcx> {
pub fn eval(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self {
match self {
Self::Ty(c) => {
// FIXME Need to use a different evaluation function that directly returns a `ConstValue`
// if evaluation succeeds and does not create a ValTree first
if let Some(val) = c.kind().try_eval(tcx, param_env) {
if let Some(val) = c.kind().try_eval_for_mir(tcx, param_env) {
match val {
Ok(val) => Self::Val(val, c.ty()),
Err(_) => Self::Ty(tcx.const_error(self.ty())),
@ -3081,6 +3089,11 @@ impl<'tcx> ConstantKind<'tcx> {
}
}
#[inline]
pub fn from_value(val: ConstValue<'tcx>, ty: Ty<'tcx>) -> Self {
Self::Val(val, ty)
}
pub fn from_bits(
tcx: TyCtxt<'tcx>,
bits: u128,
@ -3097,11 +3110,13 @@ impl<'tcx> ConstantKind<'tcx> {
Self::Val(cv, param_env_ty.value)
}
#[inline]
pub fn from_bool(tcx: TyCtxt<'tcx>, v: bool) -> Self {
let cv = ConstValue::from_bool(v);
Self::Val(cv, tcx.types.bool)
}
#[inline]
pub fn zero_sized(ty: Ty<'tcx>) -> Self {
let cv = ConstValue::Scalar(Scalar::ZST);
Self::Val(cv, ty)
@ -3112,6 +3127,12 @@ impl<'tcx> ConstantKind<'tcx> {
Self::from_bits(tcx, n as u128, ty::ParamEnv::empty().and(ty))
}
#[inline]
pub fn from_scalar(_tcx: TyCtxt<'tcx>, s: Scalar, ty: Ty<'tcx>) -> Self {
let val = ConstValue::Scalar(s);
Self::Val(val, ty)
}
/// Literals are converted to `ConstantKindVal`, const generic parameters are eagerly
/// converted to a constant, everything else becomes `Unevaluated`.
pub fn from_anon_const(
@ -3199,8 +3220,10 @@ impl<'tcx> ConstantKind<'tcx> {
}
_ => expr,
};
debug!("expr.kind: {:?}", expr.kind);
let ty = tcx.type_of(def.def_id_for_type_of());
debug!(?ty);
// FIXME(const_generics): We currently have to special case parameters because `min_const_generics`
// does not provide the parents generics to anonymous constants. We still allow generic const
@ -3224,6 +3247,7 @@ impl<'tcx> ConstantKind<'tcx> {
kind: ty::ConstKind::Param(ty::ParamConst::new(index, name)),
ty,
});
debug!(?ty_const);
return Self::Ty(ty_const);
}
@ -3253,8 +3277,12 @@ impl<'tcx> ConstantKind<'tcx> {
debug!(?span, ?param_env);
match tcx.const_eval_resolve(param_env, uneval, Some(span)) {
Ok(val) => Self::Val(val, ty),
Ok(val) => {
debug!("evaluated const value: {:?}", val);
Self::Val(val, ty)
}
Err(_) => {
debug!("error encountered during evaluation");
// Error was handled in `const_eval_resolve`. Here we just create a
// new unevaluated const and error hard later in codegen
let ty_const = tcx.mk_const(ty::ConstS {
@ -3265,11 +3293,22 @@ impl<'tcx> ConstantKind<'tcx> {
}),
ty,
});
debug!(?ty_const);
Self::Ty(ty_const)
}
}
}
pub fn from_const(c: ty::Const<'tcx>, tcx: TyCtxt<'tcx>) -> Self {
match c.val() {
ty::ConstKind::Value(valtree) => {
let const_val = tcx.valtree_to_const_val((c.ty(), valtree));
Self::Val(const_val, c.ty())
}
_ => Self::Ty(c),
}
}
}
/// A collection of projections into user types.
@ -3485,20 +3524,182 @@ fn pretty_print_const<'tcx>(
})
}
fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Result {
fmt.write_str("b\"")?;
for &c in byte_str {
for e in std::ascii::escape_default(c) {
fmt.write_char(e as char)?;
}
}
fmt.write_str("\"")?;
Ok(())
}
fn comma_sep<'tcx>(fmt: &mut Formatter<'_>, elems: Vec<ConstantKind<'tcx>>) -> fmt::Result {
let mut first = true;
for elem in elems {
if !first {
fmt.write_str(", ")?;
}
fmt.write_str(&format!("{}", elem))?;
first = false;
}
Ok(())
}
fn pretty_print_const_value<'tcx>(
val: interpret::ConstValue<'tcx>,
ct: ConstValue<'tcx>,
ty: Ty<'tcx>,
fmt: &mut Formatter<'_>,
print_types: bool,
print_ty: bool,
) -> fmt::Result {
use crate::ty::print::PrettyPrinter;
ty::tls::with(|tcx| {
let val = tcx.lift(val).unwrap();
let ct = tcx.lift(ct).unwrap();
let ty = tcx.lift(ty).unwrap();
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let cx = cx.pretty_print_const_value(val, ty, print_types)?;
fmt.write_str(&cx.into_buffer())?;
if tcx.sess.verbose() {
fmt.write_str(&format!("ConstValue({:?}: {})", ct, ty))?;
return Ok(());
}
let u8_type = tcx.types.u8;
match (ct, ty.kind()) {
// Byte/string slices, printed as (byte) string literals.
(ConstValue::Slice { data, start, end }, ty::Ref(_, inner, _)) => {
match inner.kind() {
ty::Slice(t) => {
if *t == u8_type {
// The `inspect` here is okay since we checked the bounds, and there are
// no relocations (we have an active slice reference here). We don't use
// this result to affect interpreter execution.
let byte_str = data
.inner()
.inspect_with_uninit_and_ptr_outside_interpreter(start..end);
pretty_print_byte_str(fmt, byte_str)?;
return Ok(());
}
}
ty::Str => {
// The `inspect` here is okay since we checked the bounds, and there are no
// relocations (we have an active `str` reference here). We don't use this
// result to affect interpreter execution.
let slice = data
.inner()
.inspect_with_uninit_and_ptr_outside_interpreter(start..end);
fmt.write_str(&format!("{:?}", String::from_utf8_lossy(slice)))?;
return Ok(());
}
_ => {}
}
}
(ConstValue::ByRef { alloc, offset }, ty::Array(t, n)) if *t == u8_type => {
let n = n.val().try_to_bits(tcx.data_layout.pointer_size).unwrap();
// cast is ok because we already checked for pointer size (32 or 64 bit) above
let range = AllocRange { start: offset, size: Size::from_bytes(n) };
let byte_str = alloc.inner().get_bytes(&tcx, range).unwrap();
fmt.write_str("*")?;
pretty_print_byte_str(fmt, byte_str)?;
return Ok(());
}
// Aggregates, printed as array/tuple/struct/variant construction syntax.
//
// NB: the `has_param_types_or_consts` check ensures that we can use
// the `destructure_const` query with an empty `ty::ParamEnv` without
// introducing ICEs (e.g. via `layout_of`) from missing bounds.
// E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized`
// to be able to destructure the tuple into `(0u8, *mut T)
//
// FIXME(eddyb) for `--emit=mir`/`-Z dump-mir`, we should provide the
// correct `ty::ParamEnv` to allow printing *all* constant values.
(_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_param_types_or_consts() => {
let ct = tcx.lift(ct).unwrap();
let ty = tcx.lift(ty).unwrap();
if let Some(contents) = tcx.try_destructure_mir_constant(
ty::ParamEnv::reveal_all().and(ConstantKind::Val(ct, ty)),
) {
let fields = contents.fields.iter().copied().collect::<Vec<_>>();
match *ty.kind() {
ty::Array(..) => {
fmt.write_str("[")?;
comma_sep(fmt, fields)?;
fmt.write_str("]")?;
}
ty::Tuple(..) => {
fmt.write_str("(")?;
comma_sep(fmt, fields)?;
if contents.fields.len() == 1 {
fmt.write_str(",")?;
}
fmt.write_str(")")?;
}
ty::Adt(def, _) if def.variants().is_empty() => {
fmt.write_str(&format!("{{unreachable(): {}}}", ty))?;
}
ty::Adt(def, substs) => {
let variant_idx = contents
.variant
.expect("destructed mir constant of adt without variant idx");
let variant_def = &def.variant(variant_idx);
let substs = tcx.lift(substs).unwrap();
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let cx = cx.print_value_path(variant_def.def_id, substs)?;
fmt.write_str(&cx.into_buffer())?;
match variant_def.ctor_kind {
CtorKind::Const => {}
CtorKind::Fn => {
fmt.write_str("(")?;
comma_sep(fmt, fields)?;
fmt.write_str(")")?;
}
CtorKind::Fictive => {
fmt.write_str(" {{ ")?;
let mut first = true;
for (field_def, field) in iter::zip(&variant_def.fields, fields)
{
if !first {
fmt.write_str(", ")?;
}
fmt.write_str(&format!("{}: {}", field_def.name, field))?;
first = false;
}
fmt.write_str(" }}")?;
}
}
}
_ => unreachable!(),
}
return Ok(());
} else {
// Fall back to debug pretty printing for invalid constants.
fmt.write_str(&format!("{:?}", ct))?;
if print_ty {
fmt.write_str(&format!(": {}", ty))?;
}
return Ok(());
};
}
(ConstValue::Scalar(scalar), _) => {
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let ty = tcx.lift(ty).unwrap();
cx = cx.pretty_print_const_scalar(scalar, ty, print_ty)?;
fmt.write_str(&cx.into_buffer())?;
return Ok(());
}
// FIXME(oli-obk): also pretty print arrays and other aggregate constants by reading
// their fields instead of just dumping the memory.
_ => {}
}
// fallback
fmt.write_str(&format!("{:?}", ct))?;
if print_ty {
fmt.write_str(&format!(": {}", ty))?;
}
Ok(())
})
}