1
Fork 0

Refactor the cast-then-cast cases together, and support transmute-then-transmute

This commit is contained in:
Scott McMurray 2025-01-05 22:38:38 -08:00
parent 03650dd029
commit 293f8e8941
12 changed files with 589 additions and 113 deletions

View file

@ -1366,110 +1366,112 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
return self.new_opaque();
}
let mut was_updated = false;
let mut was_ever_updated = false;
loop {
let mut was_updated_this_iteration = false;
// Transmuting `*const T` <=> `*mut T` is just a pointer cast,
// which we might be able to merge with other ones later.
if let Transmute = kind
&& let ty::RawPtr(from_pointee, _) = from.kind()
&& let ty::RawPtr(to_pointee, _) = to.kind()
&& from_pointee == to_pointee
{
*kind = PtrToPtr;
was_updated = true;
}
// If a cast just casts away the metadata again, then we can get it by
// casting the original thin pointer passed to `from_raw_parts`
if let PtrToPtr = kind
&& let Value::Aggregate(AggregateTy::RawPtr { data_pointer_ty, .. }, _, fields) =
self.get(value)
&& let ty::RawPtr(to_pointee, _) = to.kind()
&& to_pointee.is_sized(self.tcx, self.typing_env())
{
from = *data_pointer_ty;
value = fields[0];
was_updated = true;
if *data_pointer_ty == to {
return Some(fields[0]);
// Transmuting `*const T` <=> `*mut T` is just a pointer cast,
// which we might be able to merge with other ones later.
if let Transmute = kind
&& let ty::RawPtr(from_pointee, _) = from.kind()
&& let ty::RawPtr(to_pointee, _) = to.kind()
&& from_pointee == to_pointee
{
*kind = PtrToPtr;
was_updated_this_iteration = true;
}
}
// PtrToPtr-then-PtrToPtr can skip the intermediate step
if let PtrToPtr = kind
&& let Value::Cast { kind: inner_kind, value: inner_value, from: inner_from, to: _ } =
*self.get(value)
&& let PtrToPtr = inner_kind
{
from = inner_from;
value = inner_value;
was_updated = true;
if inner_from == to {
return Some(inner_value);
// If a cast just casts away the metadata again, then we can get it by
// casting the original thin pointer passed to `from_raw_parts`
if let PtrToPtr = kind
&& let Value::Aggregate(AggregateTy::RawPtr { data_pointer_ty, .. }, _, fields) =
self.get(value)
&& let ty::RawPtr(to_pointee, _) = to.kind()
&& to_pointee.is_sized(self.tcx, self.typing_env())
{
from = *data_pointer_ty;
value = fields[0];
was_updated_this_iteration = true;
if *data_pointer_ty == to {
return Some(fields[0]);
}
}
}
// Aggregate-then-Transmute can just transmute the original field value,
// so long as the bytes of a value from only from a single field.
if let Transmute = kind
&& let Value::Aggregate(
AggregateTy::Def(aggregate_did, aggregate_args),
variant_idx,
field_values,
) = self.get(value)
&& let aggregate_ty =
self.tcx.type_of(aggregate_did).instantiate(self.tcx, aggregate_args)
&& let Some((field_idx, field_ty)) =
self.value_is_all_in_one_field(aggregate_ty, *variant_idx)
{
from = field_ty;
value = field_values[field_idx.as_usize()];
was_updated = true;
if field_ty == to {
return Some(value);
// Aggregate-then-Transmute can just transmute the original field value,
// so long as the bytes of a value from only from a single field.
if let Transmute = kind
&& let Value::Aggregate(
AggregateTy::Def(aggregate_did, aggregate_args),
variant_idx,
field_values,
) = self.get(value)
&& let aggregate_ty =
self.tcx.type_of(aggregate_did).instantiate(self.tcx, aggregate_args)
&& let Some((field_idx, field_ty)) =
self.value_is_all_in_one_field(aggregate_ty, *variant_idx)
{
from = field_ty;
value = field_values[field_idx.as_usize()];
was_updated_this_iteration = true;
if field_ty == to {
return Some(value);
}
}
}
// PtrToPtr-then-Transmute can just transmute the original, so long as the
// PtrToPtr didn't change metadata (and thus the size of the pointer)
if let Transmute = kind
&& let Value::Cast {
kind: PtrToPtr,
// Various cast-then-cast cases can be simplified.
if let Value::Cast {
kind: inner_kind,
value: inner_value,
from: inner_from,
to: inner_to,
} = *self.get(value)
&& self.pointers_have_same_metadata(inner_from, inner_to)
{
from = inner_from;
value = inner_value;
was_updated = true;
if inner_from == to {
return Some(inner_value);
{
let new_kind = match (inner_kind, *kind) {
// Even if there's a narrowing cast in here that's fine, because
// things like `*mut [i32] -> *mut i32 -> *const i32` and
// `*mut [i32] -> *const [i32] -> *const i32` can skip the middle in MIR.
(PtrToPtr, PtrToPtr) => Some(PtrToPtr),
// PtrToPtr-then-Transmute is fine so long as the pointer cast is identity:
// `*const T -> *mut T -> NonNull<T>` is fine, but we need to check for narrowing
// to skip things like `*const [i32] -> *const i32 -> NonNull<T>`.
(PtrToPtr, Transmute)
if self.pointers_have_same_metadata(inner_from, inner_to) =>
{
Some(Transmute)
}
// Similarly, for Transmute-then-PtrToPtr. Note that we need to check different
// variables for their metadata, and thus this can't merge with the previous arm.
(Transmute, PtrToPtr) if self.pointers_have_same_metadata(from, to) => {
Some(Transmute)
}
// If would be legal to always do this, but we don't want to hide information
// from the backend that it'd otherwise be able to use for optimizations.
(Transmute, Transmute)
if !self.type_may_have_niche_of_interest_to_backend(inner_to) =>
{
Some(Transmute)
}
_ => None,
};
if let Some(new_kind) = new_kind {
*kind = new_kind;
from = inner_from;
value = inner_value;
was_updated_this_iteration = true;
if inner_from == to {
return Some(inner_value);
}
}
}
if was_updated_this_iteration {
was_ever_updated = true;
} else {
break;
}
}
// Transmute-then-PtrToPtr can just transmute the original, so long as the
// PtrToPtr won't change metadata (and thus the size of the pointer)
if let PtrToPtr = kind
&& let Value::Cast {
kind: Transmute,
value: inner_value,
from: inner_from,
to: _inner_to,
} = *self.get(value)
&& self.pointers_have_same_metadata(from, to)
{
*kind = Transmute;
from = inner_from;
value = inner_value;
was_updated = true;
if inner_from == to {
return Some(inner_value);
}
}
if was_updated && let Some(op) = self.try_as_operand(value, location) {
if was_ever_updated && let Some(op) = self.try_as_operand(value, location) {
*operand = op;
}
@ -1492,6 +1494,28 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
}
}
/// Returns `false` if we know for sure that this type has no interesting niche,
/// and thus we can skip transmuting through it without worrying.
///
/// The backend will emit `assume`s when transmuting between types with niches,
/// so we want to preserve `i32 -> char -> u32` so that that data is around,
/// but it's fine to skip whole-range-is-value steps like `A -> u32 -> B`.
fn type_may_have_niche_of_interest_to_backend(&self, ty: Ty<'tcx>) -> bool {
let Ok(layout) = self.ecx.layout_of(ty) else {
// If it's too generic or something, then assume it might be interesting later.
return true;
};
match layout.backend_repr {
BackendRepr::Uninhabited => true,
BackendRepr::Scalar(a) => !a.is_always_valid(&self.ecx),
BackendRepr::ScalarPair(a, b) => {
!a.is_always_valid(&self.ecx) || !b.is_always_valid(&self.ecx)
}
BackendRepr::Vector { .. } | BackendRepr::Memory { .. } => false,
}
}
fn value_is_all_in_one_field(
&self,
ty: Ty<'tcx>,