Skip to content

interpret: add allocation parameters to AllocBytes #141513

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 1 commit into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions compiler/rustc_const_eval/src/const_eval/dummy_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,9 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
) -> &'a mut Vec<interpret::Frame<'tcx, Self::Provenance, Self::FrameExtra>> {
unimplemented!()
}

fn get_default_alloc_params(
&self,
) -> <Self::Bytes as rustc_middle::mir::interpret::AllocBytes>::AllocParams {
}
}
3 changes: 3 additions & 0 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,9 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
Cow::Owned(compute_range())
}
}

fn get_default_alloc_params(&self) -> <Self::Bytes as mir::interpret::AllocBytes>::AllocParams {
}
}

// Please do not add any code below the above `Machine` trait impl. I (oli-obk) plan more cleanups
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::fluent_generated as fluent;
/// Directly returns an `Allocation` containing an absolute path representation of the given type.
pub(crate) fn alloc_type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ConstAllocation<'tcx> {
let path = crate::util::type_name(tcx, ty);
let alloc = Allocation::from_bytes_byte_aligned_immutable(path.into_bytes());
let alloc = Allocation::from_bytes_byte_aligned_immutable(path.into_bytes(), ());
tcx.mk_const_alloc(alloc)
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_const_eval/src/interpret/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@ pub trait Machine<'tcx>: Sized {
// Default to no caching.
Cow::Owned(compute_range())
}

/// Compute the value passed to the constructors of the `AllocBytes` type for
/// abstract machine allocations.
fn get_default_alloc_params(&self) -> <Self::Bytes as AllocBytes>::AllocParams;
}

/// A lot of the flexibility above is just needed for `Miri`, but all "compile-time" machines
Expand Down
8 changes: 5 additions & 3 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
kind: MemoryKind<M::MemoryKind>,
init: AllocInit,
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
let params = self.machine.get_default_alloc_params();
let alloc = if M::PANIC_ON_ALLOC_FAIL {
Allocation::new(size, align, init)
Allocation::new(size, align, init, params)
} else {
Allocation::try_new(size, align, init)?
Allocation::try_new(size, align, init, params)?
};
self.insert_allocation(alloc, kind)
}
Expand All @@ -248,7 +249,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
kind: MemoryKind<M::MemoryKind>,
mutability: Mutability,
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
let alloc = Allocation::from_bytes(bytes, align, mutability);
let params = self.machine.get_default_alloc_params();
let alloc = Allocation::from_bytes(bytes, align, mutability, params);
self.insert_allocation(alloc, kind)
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub(crate) fn create_static_alloc<'tcx>(
static_def_id: LocalDefId,
layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
let alloc = Allocation::try_new(layout.size, layout.align.abi, AllocInit::Uninit)?;
let alloc = Allocation::try_new(layout.size, layout.align.abi, AllocInit::Uninit, ())?;
let alloc_id = ecx.tcx.reserve_and_set_static_alloc(static_def_id.into());
assert_eq!(ecx.machine.static_root_ids, None);
ecx.machine.static_root_ids = Some((alloc_id, static_def_id));
Expand Down
58 changes: 41 additions & 17 deletions compiler/rustc_middle/src/mir/interpret/allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,21 @@ use crate::ty;

/// Functionality required for the bytes of an `Allocation`.
pub trait AllocBytes: Clone + fmt::Debug + Deref<Target = [u8]> + DerefMut<Target = [u8]> {
/// The type of extra parameters passed in when creating an allocation.
/// Can be used by `interpret::Machine` instances to make runtime-configuration-dependent
/// decisions about the allocation strategy.
type AllocParams;

/// Create an `AllocBytes` from a slice of `u8`.
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align) -> Self;
fn from_bytes<'a>(
slice: impl Into<Cow<'a, [u8]>>,
_align: Align,
_params: Self::AllocParams,
) -> Self;

/// Create a zeroed `AllocBytes` of the specified size and alignment.
/// Returns `None` if we ran out of memory on the host.
fn zeroed(size: Size, _align: Align) -> Option<Self>;
fn zeroed(size: Size, _align: Align, _params: Self::AllocParams) -> Option<Self>;

/// Gives direct access to the raw underlying storage.
///
Expand All @@ -51,11 +60,13 @@ pub trait AllocBytes: Clone + fmt::Debug + Deref<Target = [u8]> + DerefMut<Targe

/// Default `bytes` for `Allocation` is a `Box<u8>`.
impl AllocBytes for Box<[u8]> {
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align) -> Self {
type AllocParams = ();

fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, _align: Align, _params: ()) -> Self {
Box::<[u8]>::from(slice.into())
}

fn zeroed(size: Size, _align: Align) -> Option<Self> {
fn zeroed(size: Size, _align: Align, _params: ()) -> Option<Self> {
let bytes = Box::<[u8]>::try_new_zeroed_slice(size.bytes().try_into().ok()?).ok()?;
// SAFETY: the box was zero-allocated, which is a valid initial value for Box<[u8]>
let bytes = unsafe { bytes.assume_init() };
Expand Down Expand Up @@ -172,9 +183,8 @@ fn all_zero(buf: &[u8]) -> bool {
}

/// Custom encoder for [`Allocation`] to more efficiently represent the case where all bytes are 0.
impl<Prov: Provenance, Extra, Bytes, E: Encoder> Encodable<E> for Allocation<Prov, Extra, Bytes>
impl<Prov: Provenance, Extra, E: Encoder> Encodable<E> for Allocation<Prov, Extra, Box<[u8]>>
where
Bytes: AllocBytes,
ProvenanceMap<Prov>: Encodable<E>,
Extra: Encodable<E>,
{
Expand All @@ -192,9 +202,8 @@ where
}
}

impl<Prov: Provenance, Extra, Bytes, D: Decoder> Decodable<D> for Allocation<Prov, Extra, Bytes>
impl<Prov: Provenance, Extra, D: Decoder> Decodable<D> for Allocation<Prov, Extra, Box<[u8]>>
where
Bytes: AllocBytes,
ProvenanceMap<Prov>: Decodable<D>,
Extra: Decodable<D>,
{
Expand All @@ -203,7 +212,7 @@ where

let len = decoder.read_usize();
let bytes = if all_zero { vec![0u8; len] } else { decoder.read_raw_bytes(len).to_vec() };
let bytes = Bytes::from_bytes(bytes, align);
let bytes = <Box<[u8]> as AllocBytes>::from_bytes(bytes, align, ());

let provenance = Decodable::decode(decoder);
let init_mask = Decodable::decode(decoder);
Expand Down Expand Up @@ -395,8 +404,9 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
slice: impl Into<Cow<'a, [u8]>>,
align: Align,
mutability: Mutability,
params: <Bytes as AllocBytes>::AllocParams,
) -> Self {
let bytes = Bytes::from_bytes(slice, align);
let bytes = Bytes::from_bytes(slice, align, params);
let size = Size::from_bytes(bytes.len());
Self {
bytes,
Expand All @@ -408,14 +418,18 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
}
}

pub fn from_bytes_byte_aligned_immutable<'a>(slice: impl Into<Cow<'a, [u8]>>) -> Self {
Allocation::from_bytes(slice, Align::ONE, Mutability::Not)
pub fn from_bytes_byte_aligned_immutable<'a>(
slice: impl Into<Cow<'a, [u8]>>,
params: <Bytes as AllocBytes>::AllocParams,
) -> Self {
Allocation::from_bytes(slice, Align::ONE, Mutability::Not, params)
}

fn new_inner<R>(
size: Size,
align: Align,
init: AllocInit,
params: <Bytes as AllocBytes>::AllocParams,
fail: impl FnOnce() -> R,
) -> Result<Self, R> {
// We raise an error if we cannot create the allocation on the host.
Expand All @@ -424,7 +438,7 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
// deterministic. However, we can be non-deterministic here because all uses of const
// evaluation (including ConstProp!) will make compilation fail (via hard error
// or ICE) upon encountering a `MemoryExhausted` error.
let bytes = Bytes::zeroed(size, align).ok_or_else(fail)?;
let bytes = Bytes::zeroed(size, align, params).ok_or_else(fail)?;

Ok(Allocation {
bytes,
Expand All @@ -444,8 +458,13 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {

/// Try to create an Allocation of `size` bytes, failing if there is not enough memory
/// available to the compiler to do so.
pub fn try_new<'tcx>(size: Size, align: Align, init: AllocInit) -> InterpResult<'tcx, Self> {
Self::new_inner(size, align, init, || {
pub fn try_new<'tcx>(
size: Size,
align: Align,
init: AllocInit,
params: <Bytes as AllocBytes>::AllocParams,
) -> InterpResult<'tcx, Self> {
Self::new_inner(size, align, init, params, || {
ty::tls::with(|tcx| tcx.dcx().delayed_bug("exhausted memory during interpretation"));
InterpErrorKind::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted)
})
Expand All @@ -457,8 +476,13 @@ impl<Prov: Provenance, Bytes: AllocBytes> Allocation<Prov, (), Bytes> {
///
/// Example use case: To obtain an Allocation filled with specific data,
/// first call this function and then call write_scalar to fill in the right data.
pub fn new(size: Size, align: Align, init: AllocInit) -> Self {
match Self::new_inner(size, align, init, || {
pub fn new(
size: Size,
align: Align,
init: AllocInit,
params: <Bytes as AllocBytes>::AllocParams,
) -> Self {
match Self::new_inner(size, align, init, params, || {
panic!(
"interpreter ran out of memory: cannot create allocation of {} bytes",
size.bytes()
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,7 @@ impl<'tcx> TyCtxt<'tcx> {
/// Returns the same `AllocId` if called again with the same bytes.
pub fn allocate_bytes_dedup(self, bytes: &[u8], salt: usize) -> interpret::AllocId {
// Create an allocation that just contains these bytes.
let alloc = interpret::Allocation::from_bytes_byte_aligned_immutable(bytes);
let alloc = interpret::Allocation::from_bytes_byte_aligned_immutable(bytes, ());
let alloc = self.mk_const_alloc(alloc);
self.reserve_and_set_memory_dedup(alloc, salt)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/ty/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ pub(super) fn vtable_allocation_provider<'tcx>(
let ptr_align = tcx.data_layout.pointer_align.abi;

let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap();
let mut vtable = Allocation::new(vtable_size, ptr_align, AllocInit::Uninit);
let mut vtable = Allocation::new(vtable_size, ptr_align, AllocInit::Uninit, ());

// No need to do any alignment checks on the memory accesses below, because we know the
// allocation is correctly aligned as we created it above. Also we're only offsetting by
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_mir_build/src/builder/expr/as_constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ fn lit_to_mir_constant<'tcx>(tcx: TyCtxt<'tcx>, lit_input: LitToConstInput<'tcx>
let value = match (lit, lit_ty.kind()) {
(ast::LitKind::Str(s, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_str() => {
let s = s.as_str();
let allocation = Allocation::from_bytes_byte_aligned_immutable(s.as_bytes());
let allocation = Allocation::from_bytes_byte_aligned_immutable(s.as_bytes(), ());
let allocation = tcx.mk_const_alloc(allocation);
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
}
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _))
if matches!(inner_ty.kind(), ty::Slice(_)) =>
{
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]);
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8], ());
let allocation = tcx.mk_const_alloc(allocation);
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
}
Expand All @@ -138,7 +138,7 @@ fn lit_to_mir_constant<'tcx>(tcx: TyCtxt<'tcx>, lit_input: LitToConstInput<'tcx>
}
(ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::CStr)) =>
{
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]);
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8], ());
let allocation = tcx.mk_const_alloc(allocation);
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir_transform/src/large_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ impl EnumSizeOpt {
data,
tcx.data_layout.ptr_sized_integer().align(&tcx.data_layout).abi,
Mutability::Not,
(),
);
let alloc = tcx.reserve_and_set_memory_alloc(tcx.mk_const_alloc(alloc));
Some((*adt_def, num_discrs, *alloc_cache.entry(ty).or_insert(alloc)))
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_smir/src/rustc_smir/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub(crate) fn try_new_allocation<'tcx>(
size,
layout.align.abi,
AllocInit::Uninit,
(),
);
allocation
.write_scalar(&tables.tcx, alloc_range(Size::ZERO, size), scalar)
Expand All @@ -65,6 +66,7 @@ pub(crate) fn try_new_allocation<'tcx>(
layout.size,
layout.align.abi,
AllocInit::Uninit,
(),
);
allocation
.write_scalar(
Expand Down
6 changes: 3 additions & 3 deletions src/tools/miri/src/alloc_addresses/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
AllocKind::LiveData => {
if memory_kind == MiriMemoryKind::Global.into() {
// For new global allocations, we always pre-allocate the memory to be able use the machine address directly.
let prepared_bytes = MiriAllocBytes::zeroed(info.size, info.align)
let prepared_bytes = MiriAllocBytes::zeroed(info.size, info.align, ())
.unwrap_or_else(|| {
panic!("Miri ran out of memory: cannot create allocation of {size:?} bytes", size = info.size)
});
Expand All @@ -159,7 +159,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
AllocKind::Function | AllocKind::VTable => {
// Allocate some dummy memory to get a unique address for this function/vtable.
let alloc_bytes =
MiriAllocBytes::from_bytes(&[0u8; 1], Align::from_bytes(1).unwrap());
MiriAllocBytes::from_bytes(&[0u8; 1], Align::from_bytes(1).unwrap(), ());
let ptr = alloc_bytes.as_ptr();
// Leak the underlying memory to ensure it remains unique.
std::mem::forget(alloc_bytes);
Expand Down Expand Up @@ -429,7 +429,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
prepared_alloc_bytes.copy_from_slice(bytes);
interp_ok(prepared_alloc_bytes)
} else {
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align))
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align, ()))
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/tools/miri/src/alloc_bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl Clone for MiriAllocBytes {
fn clone(&self) -> Self {
let bytes: Cow<'_, [u8]> = Cow::Borrowed(self);
let align = Align::from_bytes(self.layout.align().to_u64()).unwrap();
MiriAllocBytes::from_bytes(bytes, align)
MiriAllocBytes::from_bytes(bytes, align, ())
}
}

Expand Down Expand Up @@ -86,7 +86,10 @@ impl MiriAllocBytes {
}

impl AllocBytes for MiriAllocBytes {
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align) -> Self {
/// Placeholder!
type AllocParams = ();

fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align, _params: ()) -> Self {
let slice = slice.into();
let size = slice.len();
let align = align.bytes();
Expand All @@ -102,7 +105,7 @@ impl AllocBytes for MiriAllocBytes {
alloc_bytes
}

fn zeroed(size: Size, align: Align) -> Option<Self> {
fn zeroed(size: Size, align: Align, _params: ()) -> Option<Self> {
let size = size.bytes();
let align = align.bytes();
// SAFETY: `alloc_fn` will only be used with `size != 0`.
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/concurrency/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let mut alloc = alloc.inner().adjust_from_tcx(
&this.tcx,
|bytes, align| {
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align))
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align, ()))
},
|ptr| this.global_root_pointer(ptr),
)?;
Expand Down
3 changes: 3 additions & 0 deletions src/tools/miri/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,9 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
) -> Cow<'e, RangeSet> {
Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range))
}

/// Placeholder!
fn get_default_alloc_params(&self) -> <Self::Bytes as AllocBytes>::AllocParams { () }
}

/// Trait for callbacks handling asynchronous machine operations.
Expand Down
Loading