-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[mir-opt] GVN some more transmute cases #133324
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
Changes from all commits
8dcc676
259c425
03650dd
293f8e8
b421a56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1366,57 +1366,108 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { | |
return self.new_opaque(); | ||
} | ||
|
||
let mut was_updated = false; | ||
|
||
// If that cast just casts away the metadata again, | ||
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]); | ||
let mut was_ever_updated = false; | ||
loop { | ||
let mut was_updated_this_iteration = false; | ||
|
||
// Transmuting between raw pointers is just a pointer cast so long as | ||
// they have the same metadata type (like `*const i32` <=> `*mut u64` | ||
// or `*mut [i32]` <=> `*const [u64]`), including the common special | ||
// case of `*const T` <=> `*mut T`. | ||
if let Transmute = kind | ||
&& from.is_unsafe_ptr() | ||
&& to.is_unsafe_ptr() | ||
&& self.pointers_have_same_metadata(from, to) | ||
{ | ||
*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]); | ||
} | ||
} | ||
} | ||
|
||
// 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, | ||
// 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(_aggregate_ty, variant_idx, field_values) = self.get(value) | ||
&& let Some((field_idx, field_ty)) = | ||
self.value_is_all_in_one_field(from, *variant_idx) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we gate this on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting point!
|
||
{ | ||
from = field_ty; | ||
value = field_values[field_idx.as_usize()]; | ||
was_updated_this_iteration = true; | ||
if field_ty == to { | ||
return Some(value); | ||
} | ||
} | ||
|
||
// 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; | ||
} | ||
} | ||
|
||
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; | ||
} | ||
|
||
|
@@ -1438,6 +1489,54 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { | |
false | ||
} | ||
} | ||
|
||
/// 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>, | ||
variant: VariantIdx, | ||
) -> Option<(FieldIdx, Ty<'tcx>)> { | ||
if let Ok(layout) = self.ecx.layout_of(ty) | ||
&& let abi::Variants::Single { index } = layout.variants | ||
&& index == variant | ||
&& let Some((field_idx, field_layout)) = layout.non_1zst_field(&self.ecx) | ||
&& layout.size == field_layout.size | ||
{ | ||
// We needed to check the variant to avoid trying to read the tag | ||
// field from an enum where no fields have variants, since that tag | ||
// field isn't in the `Aggregate` from which we're getting values. | ||
Some((FieldIdx::from_usize(field_idx), field_layout.ty)) | ||
} else if let ty::Adt(adt, args) = ty.kind() | ||
&& adt.is_struct() | ||
&& adt.repr().transparent() | ||
&& let [single_field] = adt.non_enum_variant().fields.raw.as_slice() | ||
{ | ||
Some((FieldIdx::ZERO, single_field.ty(self.tcx, args))) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
fn op_to_prop_const<'tcx>( | ||
|
Uh oh!
There was an error while loading. Please reload this page.