Auto merge of #124113 - RalfJung:interpret-scalar-ops, r=oli-obk

interpret: use ScalarInt for bin-ops; avoid PartialOrd for ScalarInt

Best reviewed commit-by-commit

r? `@oli-obk`
This commit is contained in:
bors 2024-04-19 17:00:28 +00:00
commit ce3263e60e
15 changed files with 212 additions and 153 deletions

View File

@ -110,7 +110,7 @@ pub(crate) fn codegen_const_value<'tcx>(
if fx.clif_type(layout.ty).is_some() {
return CValue::const_val(fx, layout, int);
} else {
let raw_val = int.size().truncate(int.to_bits(int.size()).unwrap());
let raw_val = int.size().truncate(int.assert_bits(int.size()));
let val = match int.size().bytes() {
1 => fx.bcx.ins().iconst(types::I8, raw_val as i64),
2 => fx.bcx.ins().iconst(types::I16, raw_val as i64),
@ -491,27 +491,24 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
return None;
}
let scalar_int = mir_operand_get_const_val(fx, operand)?;
let scalar_int = match fx
.layout_of(*ty)
.size
.cmp(&scalar_int.size())
{
Ordering::Equal => scalar_int,
Ordering::Less => match ty.kind() {
ty::Uint(_) => ScalarInt::try_from_uint(
scalar_int.try_to_uint(scalar_int.size()).unwrap(),
fx.layout_of(*ty).size,
)
.unwrap(),
ty::Int(_) => ScalarInt::try_from_int(
scalar_int.try_to_int(scalar_int.size()).unwrap(),
fx.layout_of(*ty).size,
)
.unwrap(),
_ => unreachable!(),
},
Ordering::Greater => return None,
};
let scalar_int =
match fx.layout_of(*ty).size.cmp(&scalar_int.size()) {
Ordering::Equal => scalar_int,
Ordering::Less => match ty.kind() {
ty::Uint(_) => ScalarInt::try_from_uint(
scalar_int.assert_uint(scalar_int.size()),
fx.layout_of(*ty).size,
)
.unwrap(),
ty::Int(_) => ScalarInt::try_from_int(
scalar_int.assert_int(scalar_int.size()),
fx.layout_of(*ty).size,
)
.unwrap(),
_ => unreachable!(),
},
Ordering::Greater => return None,
};
computed_scalar_int = Some(scalar_int);
}
Rvalue::Use(operand) => {

View File

@ -326,7 +326,7 @@ impl<'tcx> CValue<'tcx> {
let val = match layout.ty.kind() {
ty::Uint(UintTy::U128) | ty::Int(IntTy::I128) => {
let const_val = const_val.to_bits(layout.size).unwrap();
let const_val = const_val.assert_bits(layout.size);
let lsb = fx.bcx.ins().iconst(types::I64, const_val as u64 as i64);
let msb = fx.bcx.ins().iconst(types::I64, (const_val >> 64) as u64 as i64);
fx.bcx.ins().iconcat(lsb, msb)
@ -338,7 +338,7 @@ impl<'tcx> CValue<'tcx> {
| ty::Ref(..)
| ty::RawPtr(..)
| ty::FnPtr(..) => {
let raw_val = const_val.size().truncate(const_val.to_bits(layout.size).unwrap());
let raw_val = const_val.size().truncate(const_val.assert_bits(layout.size));
fx.bcx.ins().iconst(clif_ty, raw_val as i64)
}
ty::Float(FloatTy::F32) => {

View File

@ -295,8 +295,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
&niche_start_val,
)?
.to_scalar()
.try_to_int()
.unwrap();
.assert_int();
Ok(Some((tag, tag_field)))
}
}

View File

@ -6,9 +6,10 @@ use std::assert_matches::assert_matches;
use either::{Either, Left, Right};
use rustc_hir::def::Namespace;
use rustc_middle::mir::interpret::ScalarSizeMismatch;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter};
use rustc_middle::ty::{ConstInt, Ty, TyCtxt};
use rustc_middle::ty::{ConstInt, ScalarInt, Ty, TyCtxt};
use rustc_middle::{mir, ty};
use rustc_target::abi::{self, Abi, HasDataLayout, Size};
@ -210,6 +211,12 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
ImmTy { imm: Immediate::Uninit, layout }
}
#[inline]
pub fn from_scalar_int(s: ScalarInt, layout: TyAndLayout<'tcx>) -> Self {
assert_eq!(s.size(), layout.size);
Self::from_scalar(Scalar::from(s), layout)
}
#[inline]
pub fn try_from_uint(i: impl Into<u128>, layout: TyAndLayout<'tcx>) -> Option<Self> {
Some(Self::from_scalar(Scalar::try_from_uint(i, layout.size)?, layout))
@ -223,7 +230,6 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
pub fn try_from_int(i: impl Into<i128>, layout: TyAndLayout<'tcx>) -> Option<Self> {
Some(Self::from_scalar(Scalar::try_from_int(i, layout.size)?, layout))
}
#[inline]
pub fn from_int(i: impl Into<i128>, layout: TyAndLayout<'tcx>) -> Self {
Self::from_scalar(Scalar::from_int(i, layout.size), layout)
@ -242,6 +248,20 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
Self::from_scalar(Scalar::from_i8(c as i8), layout)
}
/// Return the immediate as a `ScalarInt`. Ensures that it has the size that the layout of the
/// immediate indicates.
#[inline]
pub fn to_scalar_int(&self) -> InterpResult<'tcx, ScalarInt> {
let s = self.to_scalar().to_scalar_int()?;
if s.size() != self.layout.size {
throw_ub!(ScalarSizeMismatch(ScalarSizeMismatch {
target_size: self.layout.size.bytes(),
data_size: s.size().bytes(),
}));
}
Ok(s)
}
#[inline]
pub fn to_const_int(self) -> ConstInt {
assert!(self.layout.ty.is_integral());

View File

@ -2,7 +2,7 @@ use rustc_apfloat::{Float, FloatConvert};
use rustc_middle::mir;
use rustc_middle::mir::interpret::{InterpResult, Scalar};
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty};
use rustc_span::symbol::sym;
use rustc_target::abi::Abi;
@ -146,14 +146,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
fn binary_int_op(
&self,
bin_op: mir::BinOp,
// passing in raw bits
l: u128,
left_layout: TyAndLayout<'tcx>,
r: u128,
right_layout: TyAndLayout<'tcx>,
left: &ImmTy<'tcx, M::Provenance>,
right: &ImmTy<'tcx, M::Provenance>,
) -> InterpResult<'tcx, (ImmTy<'tcx, M::Provenance>, bool)> {
use rustc_middle::mir::BinOp::*;
// This checks the size, so that we can just assert it below.
let l = left.to_scalar_int()?;
let r = right.to_scalar_int()?;
// Prepare to convert the values to signed or unsigned form.
let l_signed = || l.assert_int(left.layout.size);
let l_unsigned = || l.assert_uint(left.layout.size);
let r_signed = || r.assert_int(right.layout.size);
let r_unsigned = || r.assert_uint(right.layout.size);
let throw_ub_on_overflow = match bin_op {
AddUnchecked => Some(sym::unchecked_add),
SubUnchecked => Some(sym::unchecked_sub),
@ -165,69 +171,72 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Shift ops can have an RHS with a different numeric type.
if matches!(bin_op, Shl | ShlUnchecked | Shr | ShrUnchecked) {
let size = left_layout.size.bits();
let size = left.layout.size.bits();
// The shift offset is implicitly masked to the type size. (This is the one MIR operator
// that does *not* directly map to a single LLVM operation.) Compute how much we
// actually shift and whether there was an overflow due to shifting too much.
let (shift_amount, overflow) = if right_layout.abi.is_signed() {
let shift_amount = self.sign_extend(r, right_layout) as i128;
let (shift_amount, overflow) = if right.layout.abi.is_signed() {
let shift_amount = r_signed();
let overflow = shift_amount < 0 || shift_amount >= i128::from(size);
// Deliberately wrapping `as` casts: shift_amount *can* be negative, but the result
// of the `as` will be equal modulo `size` (since it is a power of two).
let masked_amount = (shift_amount as u128) % u128::from(size);
debug_assert_eq!(overflow, shift_amount != (masked_amount as i128));
assert_eq!(overflow, shift_amount != (masked_amount as i128));
(masked_amount, overflow)
} else {
let shift_amount = r;
let shift_amount = r_unsigned();
let masked_amount = shift_amount % u128::from(size);
(masked_amount, shift_amount != masked_amount)
};
let shift_amount = u32::try_from(shift_amount).unwrap(); // we masked so this will always fit
// Compute the shifted result.
let result = if left_layout.abi.is_signed() {
let l = self.sign_extend(l, left_layout) as i128;
let result = if left.layout.abi.is_signed() {
let l = l_signed();
let result = match bin_op {
Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(),
Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(),
_ => bug!(),
};
result as u128
ScalarInt::truncate_from_int(result, left.layout.size).0
} else {
match bin_op {
let l = l_unsigned();
let result = match bin_op {
Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(),
Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(),
_ => bug!(),
}
};
ScalarInt::truncate_from_uint(result, left.layout.size).0
};
let truncated = self.truncate(result, left_layout);
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
throw_ub_custom!(
fluent::const_eval_overflow_shift,
val = if right_layout.abi.is_signed() {
(self.sign_extend(r, right_layout) as i128).to_string()
val = if right.layout.abi.is_signed() {
r_signed().to_string()
} else {
r.to_string()
r_unsigned().to_string()
},
name = intrinsic_name
);
}
return Ok((ImmTy::from_uint(truncated, left_layout), overflow));
return Ok((ImmTy::from_scalar_int(result, left.layout), overflow));
}
// For the remaining ops, the types must be the same on both sides
if left_layout.ty != right_layout.ty {
if left.layout.ty != right.layout.ty {
span_bug!(
self.cur_span(),
"invalid asymmetric binary op {bin_op:?}: {l:?} ({l_ty}), {r:?} ({r_ty})",
l_ty = left_layout.ty,
r_ty = right_layout.ty,
l_ty = left.layout.ty,
r_ty = right.layout.ty,
)
}
let size = left_layout.size;
let size = left.layout.size;
// Operations that need special treatment for signed integers
if left_layout.abi.is_signed() {
if left.layout.abi.is_signed() {
let op: Option<fn(&i128, &i128) -> bool> = match bin_op {
Lt => Some(i128::lt),
Le => Some(i128::le),
@ -236,18 +245,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
_ => None,
};
if let Some(op) = op {
let l = self.sign_extend(l, left_layout) as i128;
let r = self.sign_extend(r, right_layout) as i128;
return Ok((ImmTy::from_bool(op(&l, &r), *self.tcx), false));
return Ok((ImmTy::from_bool(op(&l_signed(), &r_signed()), *self.tcx), false));
}
if bin_op == Cmp {
let l = self.sign_extend(l, left_layout) as i128;
let r = self.sign_extend(r, right_layout) as i128;
return Ok(self.three_way_compare(l, r));
return Ok(self.three_way_compare(l_signed(), r_signed()));
}
let op: Option<fn(i128, i128) -> (i128, bool)> = match bin_op {
Div if r == 0 => throw_ub!(DivisionByZero),
Rem if r == 0 => throw_ub!(RemainderByZero),
Div if r.is_null() => throw_ub!(DivisionByZero),
Rem if r.is_null() => throw_ub!(RemainderByZero),
Div => Some(i128::overflowing_div),
Rem => Some(i128::overflowing_rem),
Add | AddUnchecked => Some(i128::overflowing_add),
@ -256,8 +261,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
_ => None,
};
if let Some(op) = op {
let l = self.sign_extend(l, left_layout) as i128;
let r = self.sign_extend(r, right_layout) as i128;
let l = l_signed();
let r = r_signed();
// We need a special check for overflowing Rem and Div since they are *UB*
// on overflow, which can happen with "int_min $OP -1".
@ -272,17 +277,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
let (result, oflo) = op(l, r);
// This may be out-of-bounds for the result type, so we have to truncate ourselves.
// This may be out-of-bounds for the result type, so we have to truncate.
// If that truncation loses any information, we have an overflow.
let result = result as u128;
let truncated = self.truncate(result, left_layout);
let overflow = oflo || self.sign_extend(truncated, left_layout) != result;
let (result, lossy) = ScalarInt::truncate_from_int(result, left.layout.size);
let overflow = oflo || lossy;
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
}
return Ok((ImmTy::from_uint(truncated, left_layout), overflow));
return Ok((ImmTy::from_scalar_int(result, left.layout), overflow));
}
}
// From here on it's okay to treat everything as unsigned.
let l = l_unsigned();
let r = r_unsigned();
if bin_op == Cmp {
return Ok(self.three_way_compare(l, r));
@ -297,12 +304,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Gt => ImmTy::from_bool(l > r, *self.tcx),
Ge => ImmTy::from_bool(l >= r, *self.tcx),
BitOr => ImmTy::from_uint(l | r, left_layout),
BitAnd => ImmTy::from_uint(l & r, left_layout),
BitXor => ImmTy::from_uint(l ^ r, left_layout),
BitOr => ImmTy::from_uint(l | r, left.layout),
BitAnd => ImmTy::from_uint(l & r, left.layout),
BitXor => ImmTy::from_uint(l ^ r, left.layout),
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Rem | Div => {
assert!(!left_layout.abi.is_signed());
assert!(!left.layout.abi.is_signed());
let op: fn(u128, u128) -> (u128, bool) = match bin_op {
Add | AddUnchecked => u128::overflowing_add,
Sub | SubUnchecked => u128::overflowing_sub,
@ -316,21 +323,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let (result, oflo) = op(l, r);
// Truncate to target type.
// If that truncation loses any information, we have an overflow.
let truncated = self.truncate(result, left_layout);
let overflow = oflo || truncated != result;
let (result, lossy) = ScalarInt::truncate_from_uint(result, left.layout.size);
let overflow = oflo || lossy;
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
}
return Ok((ImmTy::from_uint(truncated, left_layout), overflow));
return Ok((ImmTy::from_scalar_int(result, left.layout), overflow));
}
_ => span_bug!(
self.cur_span(),
"invalid binary op {:?}: {:?}, {:?} (both {})",
bin_op,
l,
r,
right_layout.ty,
left,
right,
right.layout.ty,
),
};
@ -427,9 +434,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
right.layout.ty
);
let l = left.to_scalar().to_bits(left.layout.size)?;
let r = right.to_scalar().to_bits(right.layout.size)?;
self.binary_int_op(bin_op, l, left.layout, r, right.layout)
self.binary_int_op(bin_op, left, right)
}
_ if left.layout.ty.is_any_ptr() => {
// The RHS type must be a `pointer` *or an integer type* (for `Offset`).

View File

@ -87,7 +87,7 @@ impl<'tcx> ConstValue<'tcx> {
}
pub fn try_to_bits(&self, size: Size) -> Option<u128> {
self.try_to_scalar_int()?.to_bits(size).ok()
self.try_to_scalar_int()?.try_to_bits(size).ok()
}
pub fn try_to_bool(&self) -> Option<bool> {
@ -244,6 +244,8 @@ impl<'tcx> Const<'tcx> {
Const::Ty(c) => match c.kind() {
ty::ConstKind::Value(valtree) if c.ty().is_primitive() => {
// A valtree of a type where leaves directly represent the scalar const value.
// Just checking whether it is a leaf is insufficient as e.g. references are leafs
// but the leaf value is the value they point to, not the reference itself!
Some(valtree.unwrap_leaf().into())
}
_ => None,
@ -255,12 +257,22 @@ impl<'tcx> Const<'tcx> {
#[inline]
pub fn try_to_scalar_int(self) -> Option<ScalarInt> {
self.try_to_scalar()?.try_to_int().ok()
// This is equivalent to `self.try_to_scalar()?.try_to_int().ok()`, but measurably faster.
match self {
Const::Val(ConstValue::Scalar(Scalar::Int(x)), _) => Some(x),
Const::Ty(c) => match c.kind() {
ty::ConstKind::Value(valtree) if c.ty().is_primitive() => {
Some(valtree.unwrap_leaf())
}
_ => None,
},
_ => None,
}
}
#[inline]
pub fn try_to_bits(self, size: Size) -> Option<u128> {
self.try_to_scalar_int()?.to_bits(size).ok()
self.try_to_scalar_int()?.try_to_bits(size).ok()
}
#[inline]
@ -334,7 +346,7 @@ impl<'tcx> Const<'tcx> {
let int = self.try_eval_scalar_int(tcx, param_env)?;
let size =
tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(self.ty())).ok()?.size;
int.to_bits(size).ok()
int.try_to_bits(size).ok()
}
/// Panics if the value cannot be evaluated or doesn't contain a valid integer of the given type.

View File

@ -236,7 +236,7 @@ impl<Prov> Scalar<Prov> {
) -> Result<Either<u128, Pointer<Prov>>, ScalarSizeMismatch> {
assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
Ok(match self {
Scalar::Int(int) => Left(int.to_bits(target_size).map_err(|size| {
Scalar::Int(int) => Left(int.try_to_bits(target_size).map_err(|size| {
ScalarSizeMismatch { target_size: target_size.bytes(), data_size: size.bytes() }
})?),
Scalar::Ptr(ptr, sz) => {
@ -300,6 +300,11 @@ impl<'tcx, Prov: Provenance> Scalar<Prov> {
}
}
#[inline(always)]
pub fn to_scalar_int(self) -> InterpResult<'tcx, ScalarInt> {
self.try_to_int().map_err(|_| err_unsup!(ReadPointerAsInt(None)).into())
}
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980)
pub fn assert_int(self) -> ScalarInt {
@ -311,16 +316,13 @@ impl<'tcx, Prov: Provenance> Scalar<Prov> {
#[inline]
pub fn to_bits(self, target_size: Size) -> InterpResult<'tcx, u128> {
assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
self.try_to_int()
.map_err(|_| err_unsup!(ReadPointerAsInt(None)))?
.to_bits(target_size)
.map_err(|size| {
err_ub!(ScalarSizeMismatch(ScalarSizeMismatch {
target_size: target_size.bytes(),
data_size: size.bytes(),
}))
.into()
})
self.to_scalar_int()?.try_to_bits(target_size).map_err(|size| {
err_ub!(ScalarSizeMismatch(ScalarSizeMismatch {
target_size: target_size.bytes(),
data_size: size.bytes(),
}))
.into()
})
}
#[inline(always)]

View File

@ -16,7 +16,7 @@ use rustc_hir::{BindingMode, ByRef, HirId, MatchSource, RangeEnd};
use rustc_index::newtype_index;
use rustc_index::IndexVec;
use rustc_middle::middle::region;
use rustc_middle::mir::interpret::{AllocId, Scalar};
use rustc_middle::mir::interpret::AllocId;
use rustc_middle::mir::{self, BinOp, BorrowKind, FakeReadCause, UnOp};
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::layout::IntegerExt;
@ -1006,18 +1006,17 @@ impl<'tcx> PatRangeBoundary<'tcx> {
// This code is hot when compiling matches with many ranges. So we
// special-case extraction of evaluated scalars for speed, for types where
// raw data comparisons are appropriate. E.g. `unicode-normalization` has
// unsigned int comparisons are appropriate. E.g. `unicode-normalization` has
// many ranges such as '\u{037A}'..='\u{037F}', and chars can be compared
// in this way.
(Finite(mir::Const::Ty(a)), Finite(mir::Const::Ty(b)))
if matches!(ty.kind(), ty::Uint(_) | ty::Char) =>
{
return Some(a.to_valtree().cmp(&b.to_valtree()));
(Finite(a), Finite(b)) if matches!(ty.kind(), ty::Uint(_) | ty::Char) => {
if let (Some(a), Some(b)) = (a.try_to_scalar_int(), b.try_to_scalar_int()) {
let sz = ty.primitive_size(tcx);
let a = a.assert_uint(sz);
let b = b.assert_uint(sz);
return Some(a.cmp(&b));
}
}
(
Finite(mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(a)), _)),
Finite(mir::Const::Val(mir::ConstValue::Scalar(Scalar::Int(b)), _)),
) if matches!(ty.kind(), ty::Uint(_) | ty::Char) => return Some(a.cmp(&b)),
_ => {}
}

View File

@ -409,7 +409,7 @@ impl<'tcx> Const<'tcx> {
let size =
tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(self.ty())).ok()?.size;
// if `ty` does not depend on generic parameters, use an empty param_env
int.to_bits(size).ok()
int.try_to_bits(size).ok()
}
#[inline]

View File

@ -126,7 +126,7 @@ impl IntoDiagArg for ConstInt {
///
/// This is a packed struct in order to allow this type to be optimally embedded in enums
/// (like Scalar).
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[repr(packed)]
pub struct ScalarInt {
/// The first `size` bytes of `data` are the value.
@ -167,9 +167,12 @@ impl<D: Decoder> Decodable<D> for ScalarInt {
impl ScalarInt {
pub const TRUE: ScalarInt = ScalarInt { data: 1_u128, size: NonZero::new(1).unwrap() };
pub const FALSE: ScalarInt = ScalarInt { data: 0_u128, size: NonZero::new(1).unwrap() };
fn raw(data: u128, size: Size) -> Self {
Self { data, size: NonZero::new(size.bytes() as u8).unwrap() }
}
#[inline]
pub fn size(self) -> Size {
Size::from_bytes(self.size.get())
@ -196,7 +199,7 @@ impl ScalarInt {
#[inline]
pub fn null(size: Size) -> Self {
Self { data: 0, size: NonZero::new(size.bytes() as u8).unwrap() }
Self::raw(0, size)
}
#[inline]
@ -207,11 +210,15 @@ impl ScalarInt {
#[inline]
pub fn try_from_uint(i: impl Into<u128>, size: Size) -> Option<Self> {
let data = i.into();
if size.truncate(data) == data {
Some(Self { data, size: NonZero::new(size.bytes() as u8).unwrap() })
} else {
None
}
if size.truncate(data) == data { Some(Self::raw(data, size)) } else { None }
}
/// Returns the truncated result, and whether truncation changed the value.
#[inline]
pub fn truncate_from_uint(i: impl Into<u128>, size: Size) -> (Self, bool) {
let data = i.into();
let r = Self::raw(size.truncate(data), size);
(r, r.data != data)
}
#[inline]
@ -220,26 +227,27 @@ impl ScalarInt {
// `into` performed sign extension, we have to truncate
let truncated = size.truncate(i as u128);
if size.sign_extend(truncated) as i128 == i {
Some(Self { data: truncated, size: NonZero::new(size.bytes() as u8).unwrap() })
Some(Self::raw(truncated, size))
} else {
None
}
}
/// Returns the truncated result, and whether truncation changed the value.
#[inline]
pub fn truncate_from_int(i: impl Into<i128>, size: Size) -> (Self, bool) {
let data = i.into();
let r = Self::raw(size.truncate(data as u128), size);
(r, size.sign_extend(r.data) as i128 != data)
}
#[inline]
pub fn try_from_target_usize(i: impl Into<u128>, tcx: TyCtxt<'_>) -> Option<Self> {
Self::try_from_uint(i, tcx.data_layout.pointer_size)
}
#[inline]
pub fn assert_bits(self, target_size: Size) -> u128 {
self.to_bits(target_size).unwrap_or_else(|size| {
bug!("expected int of size {}, but got size {}", target_size.bytes(), size.bytes())
})
}
#[inline]
pub fn to_bits(self, target_size: Size) -> Result<u128, Size> {
pub fn try_to_bits(self, target_size: Size) -> Result<u128, Size> {
assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST");
if target_size.bytes() == u64::from(self.size.get()) {
self.check_data();
@ -249,16 +257,28 @@ impl ScalarInt {
}
}
#[inline]
pub fn assert_bits(self, target_size: Size) -> u128 {
self.try_to_bits(target_size).unwrap_or_else(|size| {
bug!("expected int of size {}, but got size {}", target_size.bytes(), size.bytes())
})
}
/// Tries to convert the `ScalarInt` to an unsigned integer of the given size.
/// Fails if the size of the `ScalarInt` is not equal to `size` and returns the
/// `ScalarInt`s size in that case.
#[inline]
pub fn try_to_uint(self, size: Size) -> Result<u128, Size> {
self.to_bits(size)
self.try_to_bits(size)
}
#[inline]
pub fn assert_uint(self, size: Size) -> u128 {
self.assert_bits(size)
}
// Tries to convert the `ScalarInt` to `u8`. Fails if the `size` of the `ScalarInt`
// in not equal to `Size { raw: 1 }` and returns the `size` value of the `ScalarInt` in
// in not equal to 1 byte and returns the `size` value of the `ScalarInt` in
// that case.
#[inline]
pub fn try_to_u8(self) -> Result<u8, Size> {
@ -266,7 +286,7 @@ impl ScalarInt {
}
/// Tries to convert the `ScalarInt` to `u16`. Fails if the size of the `ScalarInt`
/// in not equal to `Size { raw: 2 }` and returns the `size` value of the `ScalarInt` in
/// in not equal to 2 bytes and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u16(self) -> Result<u16, Size> {
@ -274,7 +294,7 @@ impl ScalarInt {
}
/// Tries to convert the `ScalarInt` to `u32`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 4 }` and returns the `size` value of the `ScalarInt` in
/// in not equal to 4 bytes and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u32(self) -> Result<u32, Size> {
@ -282,7 +302,7 @@ impl ScalarInt {
}
/// Tries to convert the `ScalarInt` to `u64`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 8 }` and returns the `size` value of the `ScalarInt` in
/// in not equal to 8 bytes and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u64(self) -> Result<u64, Size> {
@ -290,7 +310,7 @@ impl ScalarInt {
}
/// Tries to convert the `ScalarInt` to `u128`. Fails if the `size` of the `ScalarInt`
/// in not equal to `Size { raw: 16 }` and returns the `size` value of the `ScalarInt` in
/// in not equal to 16 bytes and returns the `size` value of the `ScalarInt` in
/// that case.
#[inline]
pub fn try_to_u128(self) -> Result<u128, Size> {
@ -303,7 +323,7 @@ impl ScalarInt {
}
// Tries to convert the `ScalarInt` to `bool`. Fails if the `size` of the `ScalarInt`
// in not equal to `Size { raw: 1 }` or if the value is not 0 or 1 and returns the `size`
// in not equal to 1 byte or if the value is not 0 or 1 and returns the `size`
// value of the `ScalarInt` in that case.
#[inline]
pub fn try_to_bool(self) -> Result<bool, Size> {
@ -319,40 +339,46 @@ impl ScalarInt {
/// `ScalarInt`s size in that case.
#[inline]
pub fn try_to_int(self, size: Size) -> Result<i128, Size> {
let b = self.to_bits(size)?;
let b = self.try_to_bits(size)?;
Ok(size.sign_extend(b) as i128)
}
#[inline]
pub fn assert_int(self, size: Size) -> i128 {
let b = self.assert_bits(size);
size.sign_extend(b) as i128
}
/// Tries to convert the `ScalarInt` to i8.
/// Fails if the size of the `ScalarInt` is not equal to `Size { raw: 1 }`
/// Fails if the size of the `ScalarInt` is not equal to 1 byte
/// and returns the `ScalarInt`s size in that case.
pub fn try_to_i8(self) -> Result<i8, Size> {
self.try_to_int(Size::from_bits(8)).map(|v| i8::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to i16.
/// Fails if the size of the `ScalarInt` is not equal to `Size { raw: 2 }`
/// Fails if the size of the `ScalarInt` is not equal to 2 bytes
/// and returns the `ScalarInt`s size in that case.
pub fn try_to_i16(self) -> Result<i16, Size> {
self.try_to_int(Size::from_bits(16)).map(|v| i16::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to i32.
/// Fails if the size of the `ScalarInt` is not equal to `Size { raw: 4 }`
/// Fails if the size of the `ScalarInt` is not equal to 4 bytes
/// and returns the `ScalarInt`s size in that case.
pub fn try_to_i32(self) -> Result<i32, Size> {
self.try_to_int(Size::from_bits(32)).map(|v| i32::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to i64.
/// Fails if the size of the `ScalarInt` is not equal to `Size { raw: 8 }`
/// Fails if the size of the `ScalarInt` is not equal to 8 bytes
/// and returns the `ScalarInt`s size in that case.
pub fn try_to_i64(self) -> Result<i64, Size> {
self.try_to_int(Size::from_bits(64)).map(|v| i64::try_from(v).unwrap())
}
/// Tries to convert the `ScalarInt` to i128.
/// Fails if the size of the `ScalarInt` is not equal to `Size { raw: 16 }`
/// Fails if the size of the `ScalarInt` is not equal to 16 bytes
/// and returns the `ScalarInt`s size in that case.
pub fn try_to_i128(self) -> Result<i128, Size> {
self.try_to_int(Size::from_bits(128))
@ -366,7 +392,7 @@ impl ScalarInt {
#[inline]
pub fn try_to_float<F: Float>(self) -> Result<F, Size> {
// Going through `to_uint` to check size and truncation.
Ok(F::from_bits(self.to_bits(Size::from_bits(F::BITS))?))
Ok(F::from_bits(self.try_to_bits(Size::from_bits(F::BITS))?))
}
#[inline]
@ -415,7 +441,7 @@ macro_rules! try_from {
fn try_from(int: ScalarInt) -> Result<Self, Size> {
// The `unwrap` cannot fail because to_bits (if it succeeds)
// is guaranteed to return a value that fits into the size.
int.to_bits(Size::from_bytes(std::mem::size_of::<$ty>()))
int.try_to_bits(Size::from_bytes(std::mem::size_of::<$ty>()))
.map(|u| u.try_into().unwrap())
}
}
@ -450,7 +476,7 @@ impl TryFrom<ScalarInt> for char {
#[inline]
fn try_from(int: ScalarInt) -> Result<Self, Self::Error> {
let Ok(bits) = int.to_bits(Size::from_bytes(std::mem::size_of::<char>())) else {
let Ok(bits) = int.try_to_bits(Size::from_bytes(std::mem::size_of::<char>())) else {
return Err(CharTryFromScalarInt);
};
match char::from_u32(bits.try_into().unwrap()) {
@ -472,7 +498,7 @@ impl TryFrom<ScalarInt> for Half {
type Error = Size;
#[inline]
fn try_from(int: ScalarInt) -> Result<Self, Size> {
int.to_bits(Size::from_bytes(2)).map(Self::from_bits)
int.try_to_bits(Size::from_bytes(2)).map(Self::from_bits)
}
}
@ -488,7 +514,7 @@ impl TryFrom<ScalarInt> for Single {
type Error = Size;
#[inline]
fn try_from(int: ScalarInt) -> Result<Self, Size> {
int.to_bits(Size::from_bytes(4)).map(Self::from_bits)
int.try_to_bits(Size::from_bytes(4)).map(Self::from_bits)
}
}
@ -504,7 +530,7 @@ impl TryFrom<ScalarInt> for Double {
type Error = Size;
#[inline]
fn try_from(int: ScalarInt) -> Result<Self, Size> {
int.to_bits(Size::from_bytes(8)).map(Self::from_bits)
int.try_to_bits(Size::from_bytes(8)).map(Self::from_bits)
}
}
@ -520,7 +546,7 @@ impl TryFrom<ScalarInt> for Quad {
type Error = Size;
#[inline]
fn try_from(int: ScalarInt) -> Result<Self, Size> {
int.to_bits(Size::from_bytes(16)).map(Self::from_bits)
int.try_to_bits(Size::from_bytes(16)).map(Self::from_bits)
}
}

View File

@ -3,7 +3,7 @@ use crate::mir::interpret::Scalar;
use crate::ty::{self, Ty, TyCtxt};
use rustc_macros::{HashStable, TyDecodable, TyEncodable};
#[derive(Copy, Clone, Debug, Hash, TyEncodable, TyDecodable, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Copy, Clone, Debug, Hash, TyEncodable, TyDecodable, Eq, PartialEq)]
#[derive(HashStable)]
/// This datastructure is used to represent the value of constants used in the type system.
///

View File

@ -796,7 +796,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
if let Some(ref value) = self.eval_operand(discr)
&& let Some(value_const) = self.use_ecx(|this| this.ecx.read_scalar(value))
&& let Ok(constant) = value_const.try_to_int()
&& let Ok(constant) = constant.to_bits(constant.size())
&& let Ok(constant) = constant.try_to_bits(constant.size())
{
// We managed to evaluate the discriminant, so we know we only need to visit
// one target.

View File

@ -369,8 +369,7 @@ impl<'tcx> SimplifyMatch<'tcx> for SimplifyToExp {
}
fn int_equal(l: ScalarInt, r: impl Into<u128>, size: Size) -> bool {
l.try_to_int(l.size()).unwrap()
== ScalarInt::try_from_uint(r, size).unwrap().try_to_int(size).unwrap()
l.assert_int(l.size()) == ScalarInt::try_from_uint(r, size).unwrap().assert_int(size)
}
// We first compare the two branches, and then the other branches need to fulfill the same conditions.

View File

@ -490,14 +490,14 @@ impl<'tcx> Validator<'_, 'tcx> {
}
_ => None,
};
match rhs_val.map(|x| x.try_to_uint(sz).unwrap()) {
match rhs_val.map(|x| x.assert_uint(sz)) {
// for the zero test, int vs uint does not matter
Some(x) if x != 0 => {} // okay
_ => return Err(Unpromotable), // value not known or 0 -- not okay
}
// Furthermore, for signed divison, we also have to exclude `int::MIN / -1`.
if lhs_ty.is_signed() {
match rhs_val.map(|x| x.try_to_int(sz).unwrap()) {
match rhs_val.map(|x| x.assert_int(sz)) {
Some(-1) | None => {
// The RHS is -1 or unknown, so we have to be careful.
// But is the LHS int::MIN?
@ -508,7 +508,7 @@ impl<'tcx> Validator<'_, 'tcx> {
_ => None,
};
let lhs_min = sz.signed_int_min();
match lhs_val.map(|x| x.try_to_int(sz).unwrap()) {
match lhs_val.map(|x| x.assert_int(sz)) {
Some(x) if x != lhs_min => {} // okay
_ => return Err(Unpromotable), // value not known or int::MIN -- not okay
}

View File

@ -420,7 +420,7 @@ pub(crate) mod rustc {
fn from_tag(tag: ScalarInt, tcx: TyCtxt<'tcx>) -> Self {
use rustc_target::abi::Endian;
let size = tag.size();
let bits = tag.to_bits(size).unwrap();
let bits = tag.assert_bits(size);
let bytes: [u8; 16];
let bytes = match tcx.data_layout.endian {
Endian::Little => {