Rollup merge of #144890 - WaffleLapkin:project_fields, r=lcnr

Add `InterpCx::project_fields`

I was hoping for a much bigger improvement and this is lukewarm at best ^^'

Still, I think this makes sense.
This commit is contained in:
Samuel Tardieu
2025-08-05 03:51:40 +02:00
committed by GitHub
6 changed files with 44 additions and 33 deletions

View File

@@ -199,6 +199,15 @@ where
base.offset_with_meta(offset, OffsetMode::Inbounds, meta, field_layout, self) base.offset_with_meta(offset, OffsetMode::Inbounds, meta, field_layout, self)
} }
/// Projects multiple fields at once. See [`Self::project_field`] for details.
pub fn project_fields<P: Projectable<'tcx, M::Provenance>, const N: usize>(
&self,
base: &P,
fields: [FieldIdx; N],
) -> InterpResult<'tcx, [P; N]> {
fields.try_map(|field| self.project_field(base, field))
}
/// Downcasting to an enum variant. /// Downcasting to an enum variant.
pub fn project_downcast<P: Projectable<'tcx, M::Provenance>>( pub fn project_downcast<P: Projectable<'tcx, M::Provenance>>(
&self, &self,

View File

@@ -121,25 +121,24 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized {
// `Box` has two fields: the pointer we care about, and the allocator. // `Box` has two fields: the pointer we care about, and the allocator.
assert_eq!(v.layout().fields.count(), 2, "`Box` must have exactly 2 fields"); assert_eq!(v.layout().fields.count(), 2, "`Box` must have exactly 2 fields");
let (unique_ptr, alloc) = ( let [unique_ptr, alloc] =
self.ecx().project_field(v, FieldIdx::ZERO)?, self.ecx().project_fields(v, [FieldIdx::ZERO, FieldIdx::ONE])?;
self.ecx().project_field(v, FieldIdx::ONE)?,
);
// Unfortunately there is some type junk in the way here: `unique_ptr` is a `Unique`... // Unfortunately there is some type junk in the way here: `unique_ptr` is a `Unique`...
// (which means another 2 fields, the second of which is a `PhantomData`) // (which means another 2 fields, the second of which is a `PhantomData`)
assert_eq!(unique_ptr.layout().fields.count(), 2); assert_eq!(unique_ptr.layout().fields.count(), 2);
let (nonnull_ptr, phantom) = ( let [nonnull_ptr, phantom] =
self.ecx().project_field(&unique_ptr, FieldIdx::ZERO)?, self.ecx().project_fields(&unique_ptr, [FieldIdx::ZERO, FieldIdx::ONE])?;
self.ecx().project_field(&unique_ptr, FieldIdx::ONE)?,
);
assert!( assert!(
phantom.layout().ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()), phantom.layout().ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()),
"2nd field of `Unique` should be PhantomData but is {:?}", "2nd field of `Unique` should be PhantomData but is {:?}",
phantom.layout().ty, phantom.layout().ty,
); );
// ... that contains a `NonNull`... (gladly, only a single field here) // ... that contains a `NonNull`... (gladly, only a single field here)
assert_eq!(nonnull_ptr.layout().fields.count(), 1); assert_eq!(nonnull_ptr.layout().fields.count(), 1);
let raw_ptr = self.ecx().project_field(&nonnull_ptr, FieldIdx::ZERO)?; // the actual raw ptr let raw_ptr = self.ecx().project_field(&nonnull_ptr, FieldIdx::ZERO)?; // the actual raw ptr
// ... whose only field finally is a raw ptr we can dereference. // ... whose only field finally is a raw ptr we can dereference.
self.visit_box(ty, &raw_ptr)?; self.visit_box(ty, &raw_ptr)?;

View File

@@ -2,6 +2,7 @@
#![allow(internal_features)] #![allow(internal_features)]
#![allow(rustc::diagnostic_outside_of_impl)] #![allow(rustc::diagnostic_outside_of_impl)]
#![doc(rust_logo)] #![doc(rust_logo)]
#![feature(array_try_map)]
#![feature(assert_matches)] #![feature(assert_matches)]
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(decl_macro)] #![feature(decl_macro)]

View File

@@ -42,12 +42,12 @@ fn alloc_caller_location<'tcx>(
let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap(); let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();
// Initialize fields. // Initialize fields.
ecx.write_immediate(filename, &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap()) let [filename_field, line_field, col_field] =
.expect("writing to memory we just allocated cannot fail"); ecx.project_fields(&location, [0, 1, 2].map(FieldIdx::from_u32)).unwrap();
ecx.write_scalar(line, &ecx.project_field(&location, FieldIdx::from_u32(1)).unwrap()) ecx.write_immediate(filename, &filename_field)
.expect("writing to memory we just allocated cannot fail");
ecx.write_scalar(col, &ecx.project_field(&location, FieldIdx::from_u32(2)).unwrap())
.expect("writing to memory we just allocated cannot fail"); .expect("writing to memory we just allocated cannot fail");
ecx.write_scalar(line, &line_field).expect("writing to memory we just allocated cannot fail");
ecx.write_scalar(col, &col_field).expect("writing to memory we just allocated cannot fail");
location location
} }

View File

@@ -57,6 +57,7 @@
#![feature(sized_hierarchy)] #![feature(sized_hierarchy)]
#![feature(try_blocks)] #![feature(try_blocks)]
#![feature(try_trait_v2)] #![feature(try_trait_v2)]
#![feature(try_trait_v2_residual)]
#![feature(try_trait_v2_yeet)] #![feature(try_trait_v2_yeet)]
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
#![feature(yeet_expr)] #![feature(yeet_expr)]

View File

@@ -793,36 +793,37 @@ impl Drop for Guard {
/// We also make things panic if this type is ever implicitly dropped. /// We also make things panic if this type is ever implicitly dropped.
#[derive(Debug)] #[derive(Debug)]
#[must_use] #[must_use]
pub struct InterpResult_<'tcx, T> { pub struct InterpResult<'tcx, T = ()> {
res: Result<T, InterpErrorInfo<'tcx>>, res: Result<T, InterpErrorInfo<'tcx>>,
guard: Guard, guard: Guard,
} }
// Type alias to be able to set a default type argument. impl<'tcx, T> ops::Try for InterpResult<'tcx, T> {
pub type InterpResult<'tcx, T = ()> = InterpResult_<'tcx, T>;
impl<'tcx, T> ops::Try for InterpResult_<'tcx, T> {
type Output = T; type Output = T;
type Residual = InterpResult_<'tcx, convert::Infallible>; type Residual = InterpResult<'tcx, convert::Infallible>;
#[inline] #[inline]
fn from_output(output: Self::Output) -> Self { fn from_output(output: Self::Output) -> Self {
InterpResult_::new(Ok(output)) InterpResult::new(Ok(output))
} }
#[inline] #[inline]
fn branch(self) -> ops::ControlFlow<Self::Residual, Self::Output> { fn branch(self) -> ops::ControlFlow<Self::Residual, Self::Output> {
match self.disarm() { match self.disarm() {
Ok(v) => ops::ControlFlow::Continue(v), Ok(v) => ops::ControlFlow::Continue(v),
Err(e) => ops::ControlFlow::Break(InterpResult_::new(Err(e))), Err(e) => ops::ControlFlow::Break(InterpResult::new(Err(e))),
} }
} }
} }
impl<'tcx, T> ops::FromResidual for InterpResult_<'tcx, T> { impl<'tcx, T> ops::Residual<T> for InterpResult<'tcx, convert::Infallible> {
type TryType = InterpResult<'tcx, T>;
}
impl<'tcx, T> ops::FromResidual for InterpResult<'tcx, T> {
#[inline] #[inline]
#[track_caller] #[track_caller]
fn from_residual(residual: InterpResult_<'tcx, convert::Infallible>) -> Self { fn from_residual(residual: InterpResult<'tcx, convert::Infallible>) -> Self {
match residual.disarm() { match residual.disarm() {
Err(e) => Self::new(Err(e)), Err(e) => Self::new(Err(e)),
} }
@@ -830,7 +831,7 @@ impl<'tcx, T> ops::FromResidual for InterpResult_<'tcx, T> {
} }
// Allow `yeet`ing `InterpError` in functions returning `InterpResult_`. // Allow `yeet`ing `InterpError` in functions returning `InterpResult_`.
impl<'tcx, T> ops::FromResidual<ops::Yeet<InterpErrorKind<'tcx>>> for InterpResult_<'tcx, T> { impl<'tcx, T> ops::FromResidual<ops::Yeet<InterpErrorKind<'tcx>>> for InterpResult<'tcx, T> {
#[inline] #[inline]
fn from_residual(ops::Yeet(e): ops::Yeet<InterpErrorKind<'tcx>>) -> Self { fn from_residual(ops::Yeet(e): ops::Yeet<InterpErrorKind<'tcx>>) -> Self {
Self::new(Err(e.into())) Self::new(Err(e.into()))
@@ -840,7 +841,7 @@ impl<'tcx, T> ops::FromResidual<ops::Yeet<InterpErrorKind<'tcx>>> for InterpResu
// Allow `?` on `Result<_, InterpError>` in functions returning `InterpResult_`. // Allow `?` on `Result<_, InterpError>` in functions returning `InterpResult_`.
// This is useful e.g. for `option.ok_or_else(|| err_ub!(...))`. // This is useful e.g. for `option.ok_or_else(|| err_ub!(...))`.
impl<'tcx, T, E: Into<InterpErrorInfo<'tcx>>> ops::FromResidual<Result<convert::Infallible, E>> impl<'tcx, T, E: Into<InterpErrorInfo<'tcx>>> ops::FromResidual<Result<convert::Infallible, E>>
for InterpResult_<'tcx, T> for InterpResult<'tcx, T>
{ {
#[inline] #[inline]
fn from_residual(residual: Result<convert::Infallible, E>) -> Self { fn from_residual(residual: Result<convert::Infallible, E>) -> Self {
@@ -863,7 +864,7 @@ impl<'tcx, T, V: FromIterator<T>> FromIterator<InterpResult<'tcx, T>> for Interp
} }
} }
impl<'tcx, T> InterpResult_<'tcx, T> { impl<'tcx, T> InterpResult<'tcx, T> {
#[inline(always)] #[inline(always)]
fn new(res: Result<T, InterpErrorInfo<'tcx>>) -> Self { fn new(res: Result<T, InterpErrorInfo<'tcx>>) -> Self {
Self { res, guard: Guard } Self { res, guard: Guard }
@@ -890,7 +891,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
#[inline] #[inline]
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InterpResult<'tcx, U> { pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InterpResult<'tcx, U> {
InterpResult_::new(self.disarm().map(f)) InterpResult::new(self.disarm().map(f))
} }
#[inline] #[inline]
@@ -898,7 +899,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
self, self,
f: impl FnOnce(InterpErrorInfo<'tcx>) -> InterpErrorInfo<'tcx>, f: impl FnOnce(InterpErrorInfo<'tcx>) -> InterpErrorInfo<'tcx>,
) -> InterpResult<'tcx, T> { ) -> InterpResult<'tcx, T> {
InterpResult_::new(self.disarm().map_err(f)) InterpResult::new(self.disarm().map_err(f))
} }
#[inline] #[inline]
@@ -906,7 +907,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
self, self,
f: impl FnOnce(InterpErrorKind<'tcx>) -> InterpErrorKind<'tcx>, f: impl FnOnce(InterpErrorKind<'tcx>) -> InterpErrorKind<'tcx>,
) -> InterpResult<'tcx, T> { ) -> InterpResult<'tcx, T> {
InterpResult_::new(self.disarm().map_err(|mut e| { InterpResult::new(self.disarm().map_err(|mut e| {
e.0.kind = f(e.0.kind); e.0.kind = f(e.0.kind);
e e
})) }))
@@ -914,7 +915,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
#[inline] #[inline]
pub fn inspect_err_kind(self, f: impl FnOnce(&InterpErrorKind<'tcx>)) -> InterpResult<'tcx, T> { pub fn inspect_err_kind(self, f: impl FnOnce(&InterpErrorKind<'tcx>)) -> InterpResult<'tcx, T> {
InterpResult_::new(self.disarm().inspect_err(|e| f(&e.0.kind))) InterpResult::new(self.disarm().inspect_err(|e| f(&e.0.kind)))
} }
#[inline] #[inline]
@@ -937,7 +938,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
#[inline] #[inline]
pub fn and_then<U>(self, f: impl FnOnce(T) -> InterpResult<'tcx, U>) -> InterpResult<'tcx, U> { pub fn and_then<U>(self, f: impl FnOnce(T) -> InterpResult<'tcx, U>) -> InterpResult<'tcx, U> {
InterpResult_::new(self.disarm().and_then(|t| f(t).disarm())) InterpResult::new(self.disarm().and_then(|t| f(t).disarm()))
} }
/// Returns success if both `self` and `other` succeed, while ensuring we don't /// Returns success if both `self` and `other` succeed, while ensuring we don't
@@ -952,7 +953,7 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
// Discard the other error. // Discard the other error.
drop(other.disarm()); drop(other.disarm());
// Return `self`. // Return `self`.
InterpResult_::new(Err(e)) InterpResult::new(Err(e))
} }
} }
} }
@@ -960,5 +961,5 @@ impl<'tcx, T> InterpResult_<'tcx, T> {
#[inline(always)] #[inline(always)]
pub fn interp_ok<'tcx, T>(x: T) -> InterpResult<'tcx, T> { pub fn interp_ok<'tcx, T>(x: T) -> InterpResult<'tcx, T> {
InterpResult_::new(Ok(x)) InterpResult::new(Ok(x))
} }