-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Reduce the amount of interning and layout_of
calls in const eval.
#74202
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
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a871fed
Reduce the amount of interning and `layout_of` calls in const eval.
oli-obk 5c0e172
s/try_to_usize/try_to_machine_usize/
oli-obk 763aaef
Move `ty::Const` and `ty::ConstKind` into their own modules
oli-obk ef66bf0
Make `try_eval` private
oli-obk 1bf0993
Group the try_eval functions before the eval functions
oli-obk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,111 +1,203 @@ | ||
use crate::mir::interpret::truncate; | ||
use rustc_target::abi::Size; | ||
|
||
#[derive(Copy, Clone)] | ||
/// A type for representing any integer. Only used for printing. | ||
// FIXME: Use this for the integer-tree representation needed for type level ints and | ||
// const generics? | ||
pub struct ConstInt { | ||
/// Number of bytes of the integer. Only 1, 2, 4, 8, 16 are legal values. | ||
size: u8, | ||
/// Whether the value is of a signed integer type. | ||
signed: bool, | ||
/// Whether the value is a `usize` or `isize` type. | ||
is_ptr_sized_integral: bool, | ||
/// Raw memory of the integer. All bytes beyond the `size` are unused and must be zero. | ||
raw: u128, | ||
use crate::mir::interpret::ConstValue; | ||
use crate::mir::interpret::{LitToConstInput, Scalar}; | ||
use crate::ty::subst::InternalSubsts; | ||
use crate::ty::{self, Ty, TyCtxt}; | ||
use crate::ty::{ParamEnv, ParamEnvAnd}; | ||
use rustc_errors::ErrorReported; | ||
use rustc_hir as hir; | ||
use rustc_hir::def_id::LocalDefId; | ||
use rustc_macros::HashStable; | ||
|
||
mod int; | ||
mod kind; | ||
|
||
pub use int::*; | ||
pub use kind::*; | ||
|
||
/// Typed constant value. | ||
#[derive(Copy, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Eq, PartialEq, Ord, PartialOrd)] | ||
#[derive(HashStable)] | ||
pub struct Const<'tcx> { | ||
pub ty: Ty<'tcx>, | ||
|
||
pub val: ConstKind<'tcx>, | ||
} | ||
|
||
impl ConstInt { | ||
pub fn new(raw: u128, size: Size, signed: bool, is_ptr_sized_integral: bool) -> Self { | ||
assert!(raw <= truncate(u128::MAX, size)); | ||
Self { raw, size: size.bytes() as u8, signed, is_ptr_sized_integral } | ||
#[cfg(target_arch = "x86_64")] | ||
static_assert_size!(Const<'_>, 48); | ||
|
||
impl<'tcx> Const<'tcx> { | ||
/// Literals and const generic parameters are eagerly converted to a constant, everything else | ||
/// becomes `Unevaluated`. | ||
pub fn from_anon_const(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx Self { | ||
Self::from_opt_const_arg_anon_const(tcx, ty::WithOptConstParam::unknown(def_id)) | ||
} | ||
} | ||
|
||
impl std::fmt::Debug for ConstInt { | ||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
let Self { size, signed, raw, is_ptr_sized_integral } = *self; | ||
if signed { | ||
let bit_size = size * 8; | ||
let min = 1u128 << (bit_size - 1); | ||
let max = min - 1; | ||
if raw == min { | ||
match (size, is_ptr_sized_integral) { | ||
(_, true) => write!(fmt, "isize::MIN"), | ||
(1, _) => write!(fmt, "i8::MIN"), | ||
(2, _) => write!(fmt, "i16::MIN"), | ||
(4, _) => write!(fmt, "i32::MIN"), | ||
(8, _) => write!(fmt, "i64::MIN"), | ||
(16, _) => write!(fmt, "i128::MIN"), | ||
_ => bug!("ConstInt 0x{:x} with size = {} and signed = {}", raw, size, signed), | ||
} | ||
} else if raw == max { | ||
match (size, is_ptr_sized_integral) { | ||
(_, true) => write!(fmt, "isize::MAX"), | ||
(1, _) => write!(fmt, "i8::MAX"), | ||
(2, _) => write!(fmt, "i16::MAX"), | ||
(4, _) => write!(fmt, "i32::MAX"), | ||
(8, _) => write!(fmt, "i64::MAX"), | ||
(16, _) => write!(fmt, "i128::MAX"), | ||
_ => bug!("ConstInt 0x{:x} with size = {} and signed = {}", raw, size, signed), | ||
pub fn from_opt_const_arg_anon_const( | ||
tcx: TyCtxt<'tcx>, | ||
def: ty::WithOptConstParam<LocalDefId>, | ||
) -> &'tcx Self { | ||
debug!("Const::from_anon_const(def={:?})", def); | ||
|
||
let hir_id = tcx.hir().local_def_id_to_hir_id(def.did); | ||
|
||
let body_id = match tcx.hir().get(hir_id) { | ||
hir::Node::AnonConst(ac) => ac.body, | ||
_ => span_bug!( | ||
tcx.def_span(def.did.to_def_id()), | ||
"from_anon_const can only process anonymous constants" | ||
), | ||
}; | ||
|
||
let expr = &tcx.hir().body(body_id).value; | ||
|
||
let ty = tcx.type_of(def.def_id_for_type_of()); | ||
|
||
let lit_input = match expr.kind { | ||
hir::ExprKind::Lit(ref lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: false }), | ||
hir::ExprKind::Unary(hir::UnOp::UnNeg, ref expr) => match expr.kind { | ||
hir::ExprKind::Lit(ref lit) => { | ||
Some(LitToConstInput { lit: &lit.node, ty, neg: true }) | ||
} | ||
_ => None, | ||
}, | ||
_ => None, | ||
}; | ||
|
||
if let Some(lit_input) = lit_input { | ||
// If an error occurred, ignore that it's a literal and leave reporting the error up to | ||
// mir. | ||
if let Ok(c) = tcx.at(expr.span).lit_to_const(lit_input) { | ||
return c; | ||
} else { | ||
match size { | ||
1 => write!(fmt, "{}", raw as i8)?, | ||
2 => write!(fmt, "{}", raw as i16)?, | ||
4 => write!(fmt, "{}", raw as i32)?, | ||
8 => write!(fmt, "{}", raw as i64)?, | ||
16 => write!(fmt, "{}", raw as i128)?, | ||
_ => bug!("ConstInt 0x{:x} with size = {} and signed = {}", raw, size, signed), | ||
} | ||
if fmt.alternate() { | ||
match (size, is_ptr_sized_integral) { | ||
(_, true) => write!(fmt, "_isize")?, | ||
(1, _) => write!(fmt, "_i8")?, | ||
(2, _) => write!(fmt, "_i16")?, | ||
(4, _) => write!(fmt, "_i32")?, | ||
(8, _) => write!(fmt, "_i64")?, | ||
(16, _) => write!(fmt, "_i128")?, | ||
_ => bug!(), | ||
} | ||
} | ||
Ok(()) | ||
tcx.sess.delay_span_bug(expr.span, "Const::from_anon_const: couldn't lit_to_const"); | ||
} | ||
} else { | ||
let max = truncate(u128::MAX, Size::from_bytes(size)); | ||
if raw == max { | ||
match (size, is_ptr_sized_integral) { | ||
(_, true) => write!(fmt, "usize::MAX"), | ||
(1, _) => write!(fmt, "u8::MAX"), | ||
(2, _) => write!(fmt, "u16::MAX"), | ||
(4, _) => write!(fmt, "u32::MAX"), | ||
(8, _) => write!(fmt, "u64::MAX"), | ||
(16, _) => write!(fmt, "u128::MAX"), | ||
_ => bug!("ConstInt 0x{:x} with size = {} and signed = {}", raw, size, signed), | ||
} | ||
} else { | ||
match size { | ||
1 => write!(fmt, "{}", raw as u8)?, | ||
2 => write!(fmt, "{}", raw as u16)?, | ||
4 => write!(fmt, "{}", raw as u32)?, | ||
8 => write!(fmt, "{}", raw as u64)?, | ||
16 => write!(fmt, "{}", raw as u128)?, | ||
_ => bug!("ConstInt 0x{:x} with size = {} and signed = {}", raw, size, signed), | ||
} | ||
if fmt.alternate() { | ||
match (size, is_ptr_sized_integral) { | ||
(_, true) => write!(fmt, "_usize")?, | ||
(1, _) => write!(fmt, "_u8")?, | ||
(2, _) => write!(fmt, "_u16")?, | ||
(4, _) => write!(fmt, "_u32")?, | ||
(8, _) => write!(fmt, "_u64")?, | ||
(16, _) => write!(fmt, "_u128")?, | ||
_ => bug!(), | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
// Unwrap a block, so that e.g. `{ P }` is recognised as a parameter. Const arguments | ||
// currently have to be wrapped in curly brackets, so it's necessary to special-case. | ||
let expr = match &expr.kind { | ||
hir::ExprKind::Block(block, _) if block.stmts.is_empty() && block.expr.is_some() => { | ||
block.expr.as_ref().unwrap() | ||
} | ||
_ => expr, | ||
}; | ||
|
||
use hir::{def::DefKind::ConstParam, def::Res, ExprKind, Path, QPath}; | ||
let val = match expr.kind { | ||
ExprKind::Path(QPath::Resolved(_, &Path { res: Res::Def(ConstParam, def_id), .. })) => { | ||
// Find the name and index of the const parameter by indexing the generics of | ||
// the parent item and construct a `ParamConst`. | ||
let hir_id = tcx.hir().as_local_hir_id(def_id.expect_local()); | ||
let item_id = tcx.hir().get_parent_node(hir_id); | ||
let item_def_id = tcx.hir().local_def_id(item_id); | ||
let generics = tcx.generics_of(item_def_id.to_def_id()); | ||
let index = | ||
generics.param_def_id_to_index[&tcx.hir().local_def_id(hir_id).to_def_id()]; | ||
let name = tcx.hir().name(hir_id); | ||
ty::ConstKind::Param(ty::ParamConst::new(index, name)) | ||
} | ||
_ => ty::ConstKind::Unevaluated( | ||
def.to_global(), | ||
InternalSubsts::identity_for_item(tcx, def.did.to_def_id()), | ||
None, | ||
), | ||
}; | ||
|
||
tcx.mk_const(ty::Const { val, ty }) | ||
} | ||
|
||
#[inline] | ||
/// Interns the given value as a constant. | ||
pub fn from_value(tcx: TyCtxt<'tcx>, val: ConstValue<'tcx>, ty: Ty<'tcx>) -> &'tcx Self { | ||
tcx.mk_const(Self { val: ConstKind::Value(val), ty }) | ||
} | ||
|
||
#[inline] | ||
/// Interns the given scalar as a constant. | ||
pub fn from_scalar(tcx: TyCtxt<'tcx>, val: Scalar, ty: Ty<'tcx>) -> &'tcx Self { | ||
Self::from_value(tcx, ConstValue::Scalar(val), ty) | ||
} | ||
|
||
#[inline] | ||
/// Creates a constant with the given integer value and interns it. | ||
pub fn from_bits(tcx: TyCtxt<'tcx>, bits: u128, ty: ParamEnvAnd<'tcx, Ty<'tcx>>) -> &'tcx Self { | ||
let size = tcx | ||
.layout_of(ty) | ||
.unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) | ||
.size; | ||
Self::from_scalar(tcx, Scalar::from_uint(bits, size), ty.value) | ||
} | ||
|
||
#[inline] | ||
/// Creates an interned zst constant. | ||
pub fn zero_sized(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> &'tcx Self { | ||
Self::from_scalar(tcx, Scalar::zst(), ty) | ||
} | ||
|
||
#[inline] | ||
/// Creates an interned bool constant. | ||
pub fn from_bool(tcx: TyCtxt<'tcx>, v: bool) -> &'tcx Self { | ||
Self::from_bits(tcx, v as u128, ParamEnv::empty().and(tcx.types.bool)) | ||
} | ||
|
||
#[inline] | ||
/// Creates an interned usize constant. | ||
pub fn from_usize(tcx: TyCtxt<'tcx>, n: u64) -> &'tcx Self { | ||
Self::from_bits(tcx, n as u128, ParamEnv::empty().and(tcx.types.usize)) | ||
} | ||
|
||
#[inline] | ||
/// Attempts to evaluate the given constant to bits. Can fail to evaluate in the presence of | ||
/// generics (or erroneous code) or if the value can't be represented as bits (e.g. because it | ||
/// contains const generic parameters or pointers). | ||
pub fn try_eval_bits( | ||
&self, | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ParamEnv<'tcx>, | ||
ty: Ty<'tcx>, | ||
) -> Option<u128> { | ||
assert_eq!(self.ty, ty); | ||
let size = tcx.layout_of(param_env.with_reveal_all().and(ty)).ok()?.size; | ||
// if `ty` does not depend on generic parameters, use an empty param_env | ||
self.val.eval(tcx, param_env).try_to_bits(size) | ||
} | ||
|
||
#[inline] | ||
pub fn try_eval_bool(&self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option<bool> { | ||
self.val.eval(tcx, param_env).try_to_bool() | ||
} | ||
|
||
#[inline] | ||
pub fn try_eval_usize(&self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option<u64> { | ||
self.val.eval(tcx, param_env).try_to_machine_usize(tcx) | ||
} | ||
|
||
#[inline] | ||
/// Tries to evaluate the constant if it is `Unevaluated`. If that doesn't succeed, return the | ||
/// unevaluated constant. | ||
pub fn eval(&self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> &Const<'tcx> { | ||
if let Some(val) = self.val.try_eval(tcx, param_env) { | ||
match val { | ||
Ok(val) => Const::from_value(tcx, val, self.ty), | ||
Err(ErrorReported) => tcx.const_error(self.ty), | ||
} | ||
} else { | ||
self | ||
} | ||
} | ||
|
||
#[inline] | ||
/// Panics if the value cannot be evaluated or doesn't contain a valid integer of the given type. | ||
pub fn eval_bits(&self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> u128 { | ||
self.try_eval_bits(tcx, param_env, ty) | ||
.unwrap_or_else(|| bug!("expected bits of {:#?}, got {:#?}", ty, self)) | ||
} | ||
|
||
#[inline] | ||
/// Panics if the value cannot be evaluated or doesn't contain a valid `usize`. | ||
pub fn eval_usize(&self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> u64 { | ||
self.try_eval_usize(tcx, param_env) | ||
.unwrap_or_else(|| bug!("expected usize, got {:#?}", self)) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.