Skip to content

Get rid of the Scalar and ScalarPair variants of ConstValue... #55260

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

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
edf94c5
Update function names in comments
oli-obk Oct 19, 2018
856eaed
Simplify `ConstValue`
oli-obk Oct 21, 2018
fe4e950
Make the HashStable impl of Allocation safer for future changes of th…
oli-obk Oct 21, 2018
04fc561
The `Hash` and `Eq` impls of `ConstValue` were inherently broken
oli-obk Oct 21, 2018
b11b3cb
Fixup a bunch of things for "simplify const value"
oli-obk Oct 21, 2018
3aecb0d
Fix some simd intrinsics
oli-obk Oct 21, 2018
8d42376
Fix an ICE that can occur while errors are already being emitted
oli-obk Oct 21, 2018
847f5b9
Const prop is only interested in immediate constants right now
oli-obk Oct 21, 2018
e52644c
Update a few tests with their improved diagnostic messages
oli-obk Oct 21, 2018
8246dd4
Alignment check facepalm
oli-obk Oct 21, 2018
5620d68
Enums can produce scalar pairs with undefs in one of the elements
oli-obk Oct 21, 2018
fc6472c
Hide a constructor function that is unused outside this module
oli-obk Oct 22, 2018
336094f
Use a more appropriate parameter environment
oli-obk Oct 22, 2018
b4ba332
Don't resolve `impl Trait` in function def constants to the concrete …
oli-obk Oct 22, 2018
dfc5d26
Constants carry their type with them
oli-obk Oct 22, 2018
c50d77d
Alignment of `const_field` results must be of the field's type
oli-obk Oct 22, 2018
1d14a84
Add a debugging aid
oli-obk Oct 22, 2018
9fbce39
Change some useless `ParamEnv::empty` to `reveal_all`
oli-obk Oct 22, 2018
1f2fcee
Improve debugging output of layout computation failure
oli-obk Oct 22, 2018
e3bbe6d
Shrink allocations to fit the type at hand
oli-obk Oct 22, 2018
6ff70da
Resize `Allocation`s when doing field accesses into them
oli-obk Oct 22, 2018
7b526d8
Read the slice length from the correct place in the fat pointer
oli-obk Oct 22, 2018
9df63e9
Global Allocations don't have an `extra` value
oli-obk Oct 22, 2018
a7a92e2
Rebase fallout
oli-obk Oct 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions src/librustc/ich/impls_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,6 @@ for ::mir::interpret::ConstValue<'gcx> {
def_id.hash_stable(hcx, hasher);
substs.hash_stable(hcx, hasher);
}
Scalar(val) => {
val.hash_stable(hcx, hasher);
}
ScalarPair(a, b) => {
a.hash_stable(hcx, hasher);
b.hash_stable(hcx, hasher);
}
ByRef(id, alloc, offset) => {
id.hash_stable(hcx, hasher);
alloc.hash_stable(hcx, hasher);
Expand Down Expand Up @@ -512,7 +505,7 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
hasher: &mut StableHasher<W>) {
use mir::interpret::EvalErrorKind::*;

mem::discriminant(&self).hash_stable(hcx, hasher);
mem::discriminant(self).hash_stable(hcx, hasher);

match *self {
FunctionArgCountMismatch |
Expand Down Expand Up @@ -577,11 +570,11 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
NoMirFor(ref s) => s.hash_stable(hcx, hasher),
UnterminatedCString(ptr) => ptr.hash_stable(hcx, hasher),
PointerOutOfBounds {
ptr,
offset,
access,
allocation_size,
} => {
ptr.hash_stable(hcx, hasher);
offset.hash_stable(hcx, hasher);
access.hash_stable(hcx, hasher);
allocation_size.hash_stable(hcx, hasher)
},
Expand Down
8 changes: 4 additions & 4 deletions src/librustc/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pub enum EvalErrorKind<'tcx, O> {
InvalidBool,
InvalidDiscriminant(u128),
PointerOutOfBounds {
ptr: Pointer,
offset: Size,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this way we lose the information which allocation it pointed to, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allocations don't know which AllocIds (there might be multiple) point to it. So if I have a method on an Allocation, I can't create an error containing an AllocId except by passing it as an argument just used for the error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a great regression, but well. :/

However:

there might be multiple

Eh, what?!??

Copy link
Contributor Author

@oli-obk oli-obk Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not a great regression, but well. :/

we can store the AllocId of an Allocation in the Allocation itself if you prefer that

Eh, what?!??

that can happen easily as we don't have a deduplication cache anymore. We used to have one, but it got removed during some parallel rustc PRs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deduplication of what? We are still interning, which deduplicates, right?

I think for now I can live with this regression, it's not like the AllocId would mean terribly much to most users. It was sometimes helpful during debugging, but only with a full trace and that trace would probably still have the ID somewhere right before the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating AllocIds for string literals will deduplicate the memory of the content, but still generate a new AllocId

access: bool,
allocation_size: Size,
},
Expand Down Expand Up @@ -440,10 +440,10 @@ impl<'tcx, O: fmt::Debug> fmt::Debug for EvalErrorKind<'tcx, O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::EvalErrorKind::*;
match *self {
PointerOutOfBounds { ptr, access, allocation_size } => {
write!(f, "{} at offset {}, outside bounds of allocation {} which has size {}",
PointerOutOfBounds { offset, access, allocation_size } => {
write!(f, "{} at offset {}, outside bounds of allocation with size {}",
if access { "memory access" } else { "pointer computed" },
ptr.offset.bytes(), ptr.alloc_id, allocation_size.bytes())
offset.bytes(), allocation_size.bytes())
},
MemoryLockViolation { ptr, len, frame, access, ref lock } => {
write!(f, "{:?} access by frame {} at {:?}, size {}, is in conflict with lock {:?}",
Expand Down
142 changes: 140 additions & 2 deletions src/librustc/mir/interpret/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub use self::value::{Scalar, ConstValue};
use std::fmt;
use mir;
use hir::def_id::DefId;
use ty::{self, TyCtxt, Instance};
use ty::{self, TyCtxt, Instance, Ty, ParamEnvAnd};
use ty::layout::{self, Align, HasDataLayout, Size};
use middle::region;
use std::iter;
Expand Down Expand Up @@ -545,7 +545,7 @@ pub struct Allocation<Tag=(),Extra=()> {
pub extra: Extra,
}

impl<Tag, Extra: Default> Allocation<Tag, Extra> {
impl<Tag: Copy, Extra: Default> Allocation<Tag, Extra> {
/// Creates a read-only allocation initialized by the given bytes
pub fn from_bytes(slice: &[u8], align: Align) -> Self {
let mut undef_mask = UndefMask::new(Size::ZERO);
Expand Down Expand Up @@ -575,6 +575,144 @@ impl<Tag, Extra: Default> Allocation<Tag, Extra> {
extra: Extra::default(),
}
}

#[inline]
pub fn size(&self) -> Size {
Size::from_bytes(self.bytes.len() as u64)
}

pub fn check_align(
&self,
offset: Size,
required_align: Align,
) -> EvalResult<'tcx> {
if self.align.abi() > required_align.abi() {
return err!(AlignmentCheckFailed {
has: self.align,
required: required_align,
});
}
let offset = offset.bytes();
if offset % required_align.abi() == 0 {
Ok(())
} else {
let has = offset % required_align.abi();
err!(AlignmentCheckFailed {
has: Align::from_bytes(has, has).unwrap(),
required: required_align,
})
}
}

pub fn check_bounds(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a doc comment defining "inbounds". Is one-past-the-end accepted or not?

&self,
offset: Size,
size: Size,
access: bool,
) -> EvalResult<'tcx> {
let end = offset + size;
Copy link
Member

@RalfJung RalfJung Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if this overflows? (Same below in check_defined.)

let allocation_size = self.size();
if end > allocation_size {
err!(PointerOutOfBounds { offset, access, allocation_size })
} else {
Ok(())
}
}

pub fn check_defined(
&self,
offset: Size,
size: Size,
) -> EvalResult<'tcx> {
self.undef_mask.is_range_defined(
offset,
offset + size,
).or_else(|idx| err!(ReadUndefBytes(idx)))
}

pub fn check_relocations(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is this "checking"?

I spent quite some time giving all of these methods extensive docs in memory.rs, please don't drop these :)

&self,
hdl: impl HasDataLayout,
offset: Size,
size: Size,
) -> EvalResult<'tcx> {
if self.relocations(hdl, offset, size)?.len() != 0 {
err!(ReadPointerAsBytes)
} else {
Ok(())
}
}

pub fn relocations(
&self,
hdl: impl HasDataLayout,
offset: Size,
size: Size,
) -> EvalResult<'tcx, &[(Size, (Tag, AllocId))]> {
// We have to go back `pointer_size - 1` bytes, as that one would still overlap with
// the beginning of this range.
let start = offset.bytes().saturating_sub(hdl.pointer_size().bytes() - 1);
let end = offset + size; // this does overflow checking
Ok(self.relocations.range(Size::from_bytes(start)..end))
}

pub fn get_bytes(
Copy link
Member

@RalfJung RalfJung Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a doc comment explaining exactly which checks are performed, and which not. (Internal relocations? Relocations on the edges?)

Also, if memory.rs uses this in copy, it is required to provide an address stability guarantee on its return value.

&self,
hdl: impl HasDataLayout,
offset: Size,
size: Size,
required_align: Align,
) -> EvalResult<'tcx, &[u8]> {
self.check_align(offset, required_align)?;
self.check_bounds(offset, size, true)?;
self.check_defined(offset, size)?;
self.check_relocations(hdl, offset, size)?;
Ok(self.bytes_ignoring_relocations_and_undef(offset, size))
}

pub fn read_bits(
&self,
tcx: TyCtxt<'_, '_, 'tcx>,
offset: Size,
ty: ParamEnvAnd<'tcx, Ty<'tcx>>,
) -> EvalResult<'tcx, u128> {
let ty = tcx.lift_to_global(&ty).unwrap();
let layout = tcx.layout_of(ty).unwrap_or_else(|e| {
panic!("could not compute layout for {:?}: {:?}", ty, e)
});
let bytes = self.get_bytes(tcx, offset, layout.size, layout.align)?;
Ok(read_target_uint(tcx.data_layout.endian, bytes).unwrap())
}

pub fn read_scalar(
&self,
hdl: impl HasDataLayout,
offset: Size,
) -> EvalResult<'tcx, Scalar<Tag>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, why does get_bytes return a Scalar<Tag>? That doesn't make any sense, a method of that name should return a &[u8]. Now I am even more puzzled about what it does.

(See, a doc comment would have been useful. ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is called read_scalar?

Copy link
Member

@RalfJung RalfJung Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm did the diff view fool me? Yes it did.

I am still quite confused why this looks so different from what we do in memory.rs?

let size = hdl.data_layout().pointer_size;
let required_align = hdl.data_layout().pointer_align;
self.check_align(offset, required_align)?;
self.check_bounds(offset, size, true)?;
self.check_defined(offset, size)?;
let bytes = self.bytes_ignoring_relocations_and_undef(offset, size);
let offset = read_target_uint(hdl.data_layout().endian, &bytes).unwrap();
let offset = Size::from_bytes(offset as u64);
if let Some(&(tag, alloc_id)) = self.relocations.get(&offset) {
Ok(Pointer::new_with_tag(alloc_id, offset, tag).into())
} else {
Ok(Scalar::Bits {
bits: offset.bytes() as u128,
size: size.bytes() as u8,
})
}
}

fn bytes_ignoring_relocations_and_undef(&self, offset: Size, size: Size) -> &[u8] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also ignoring alignment and bounds... wtf? We didn't have anything nearly as unsafe before.

let end = offset + size;
let offset = offset.bytes() as usize;
let end = end.bytes() as usize;
&self.bytes[offset..end]
}
}

impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {}
Expand Down
130 changes: 102 additions & 28 deletions src/librustc/mir/interpret/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

#![allow(unknown_lints)]

use ty::layout::{HasDataLayout, Size};
use ty::layout::{HasDataLayout, Align, Size, TyLayout};
use ty::subst::Substs;
use ty;
use hir::def_id::DefId;

use super::{EvalResult, Pointer, PointerArithmetic, Allocation, AllocId, sign_extend, truncate};
Expand All @@ -25,57 +26,130 @@ pub enum ConstValue<'tcx> {
/// evaluation
Unevaluated(DefId, &'tcx Substs<'tcx>),

/// Used only for types with layout::abi::Scalar ABI and ZSTs
///
/// Not using the enum `Value` to encode that this must not be `Undef`
Scalar(Scalar),

/// Used only for *fat pointers* with layout::abi::ScalarPair
///
/// Needed for pattern matching code related to slices and strings.
ScalarPair(Scalar, Scalar),

/// An allocation + offset into the allocation.
/// Invariant: The AllocId matches the allocation.
ByRef(AllocId, &'tcx Allocation, Size),
}

impl<'tcx> ConstValue<'tcx> {
#[inline]
pub fn try_to_scalar(&self) -> Option<Scalar> {
match *self {
ConstValue::Unevaluated(..) |
ConstValue::ByRef(..) |
ConstValue::ScalarPair(..) => None,
ConstValue::Scalar(val) => Some(val),
pub fn try_as_by_ref(&self) -> Option<(AllocId, &'tcx Allocation, Size)> {
match self {
ConstValue::Unevaluated(..) => None,
ConstValue::ByRef(a, b, c) => Some((*a, *b, *c)),
}
}

#[inline]
pub fn try_to_bits(&self, size: Size) -> Option<u128> {
self.try_to_scalar()?.to_bits(size).ok()
/// if this is ByRef, return the same thing but with the offset increased by `n`
pub fn try_offset(&self, n: Size) -> Option<Self> {
let (id, alloc, offset) = self.try_as_by_ref()?;
Some(ConstValue::ByRef(id, alloc, offset + n))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have complex and subtle code to do overflow-aware (which is architecture-dependent) pointer arithmetic. We surely shouldn't just do plain addition here.

}

#[inline]
pub fn try_get_bytes(&self, hdl: impl HasDataLayout, n: Size, align: Align) -> Option<&[u8]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hdl? We usually called this cx, didn't we?

let (_, alloc, offset) = self.try_as_by_ref()?;
alloc.get_bytes(hdl, offset, n, align).ok()
}

#[inline]
pub fn try_to_bits(&self, hdl: impl HasDataLayout, layout: TyLayout<'tcx>) -> Option<u128> {
let bytes = self.try_get_bytes(hdl, layout.size, layout.align)?;
let endian = hdl.data_layout().endian;
super::read_target_uint(endian, &bytes).ok()
}

#[inline]
pub fn try_to_ptr(&self) -> Option<Pointer> {
self.try_to_scalar()?.to_ptr().ok()
pub fn try_to_usize(&self, hdl: impl HasDataLayout) -> Option<u128> {
let size = hdl.data_layout().pointer_size;
let align = hdl.data_layout().pointer_align;
let bytes = self.try_get_bytes(hdl, size, align)?;
let endian = hdl.data_layout().endian;
super::read_target_uint(endian, &bytes).ok()
}

#[inline]
pub fn try_to_ptr(
&self,
hdl: impl HasDataLayout,
) -> Option<Pointer> {
let (_, alloc, offset) = self.try_as_by_ref()?;
alloc.read_scalar(hdl, offset).ok()?.to_ptr().ok()
}

/// e.g. for vtables, fat pointers or single pointers
#[inline]
pub fn new_pointer_list(
list: &[Scalar],
tcx: ty::TyCtxt<'_, '_, 'tcx>,
) -> Self {
let ps = tcx.data_layout().pointer_size;
let mut alloc = Allocation::undef(
ps * list.len() as u64,
tcx.data_layout().pointer_align,
);
alloc.undef_mask.set_range_inbounds(Size::ZERO, ps * list.len() as u64, true);
for (i, s) in list.iter().enumerate() {
let (int, ptr) = match s {
Scalar::Bits { bits, size } => {
assert!(*size as u64 == ps.bytes());
(*bits as u64, None)
}
Scalar::Ptr(ptr) => (ptr.offset.bytes(), Some(ptr)),
};
let i = i * ps.bytes() as usize;
let j = i + ps.bytes() as usize;
super::write_target_uint(
tcx.data_layout().endian,
&mut alloc.bytes[i..j],
int.into(),
).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, so here we are duplicating write_sclar? Not good. You moved so many methods here, including read_scalar, why not also write_scalar?

if let Some(ptr) = ptr {
alloc.relocations.insert(
ps * i as u64,
(ptr.tag, ptr.alloc_id),
);
}
}
Self::from_allocation(tcx, alloc)
}

#[inline]
pub fn from_allocation(
tcx: ty::TyCtxt<'_, '_, 'tcx>,
alloc: Allocation,
) -> Self {
let alloc = tcx.intern_const_alloc(alloc);
let alloc_id = tcx.alloc_map.lock().allocate(alloc);
ConstValue::ByRef(alloc_id, alloc, Size::ZERO)
}

#[inline]
pub fn new_slice(
val: Scalar,
len: u64,
cx: impl HasDataLayout
tcx: ty::TyCtxt<'_, '_, 'tcx>,
) -> Self {
ConstValue::ScalarPair(val, Scalar::Bits {
bits: len as u128,
size: cx.data_layout().pointer_size.bytes() as u8,
})
Self::new_pointer_list(
&[
val,
Scalar::Bits {
bits: len as u128,
size: tcx.data_layout.pointer_size.bytes() as u8,
},
],
tcx,
)
}

#[inline]
pub fn new_dyn_trait(val: Scalar, vtable: Pointer) -> Self {
ConstValue::ScalarPair(val, Scalar::Ptr(vtable))
pub fn new_dyn_trait(
val: Scalar,
vtable: Pointer,
tcx: ty::TyCtxt<'_, '_, 'tcx>,
) -> Self {
Self::new_pointer_list(&[val, vtable.into()], tcx)
}
}

Expand Down
Loading