Skip to content
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

Implement pointee metadata unsizing via a TypedMetadata<T> container #97052

Closed
wants to merge 1 commit into from
Closed
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
38 changes: 26 additions & 12 deletions compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use rustc_span::symbol::sym;
use rustc_span::{DebuggerVisualizerFile, DebuggerVisualizerType};
use rustc_target::abi::{Align, VariantIdx};

use std::assert_matches::assert_matches;
use std::collections::BTreeSet;
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -197,21 +198,21 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
src_ty: Ty<'tcx>,
dst_ty: Ty<'tcx>,
old_info: Option<Bx::Value>,
) -> (Bx::Value, Bx::Value) {
) -> OperandValue<Bx::Value> {
Copy link
Member

Choose a reason for hiding this comment

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

This will need to be implemented in cg_clif too. I can do this myself once this PR is merged.

debug!("unsize_ptr: {:?} => {:?}", src_ty, dst_ty);
let src_layout = bx.cx().layout_of(src_ty);
let dst_layout = bx.cx().layout_of(dst_ty);
match (src_ty.kind(), dst_ty.kind()) {
(&ty::Ref(_, a, _), &ty::Ref(_, b, _) | &ty::RawPtr(ty::TypeAndMut { ty: b, .. }))
| (&ty::RawPtr(ty::TypeAndMut { ty: a, .. }), &ty::RawPtr(ty::TypeAndMut { ty: b, .. })) => {
assert_eq!(bx.cx().type_is_sized(a), old_info.is_none());
let ptr_ty = bx.cx().type_ptr_to(bx.cx().backend_type(bx.cx().layout_of(b)));
(bx.pointercast(src, ptr_ty), unsized_info(bx, a, b, old_info))
OperandValue::Pair(bx.pointercast(src, ptr_ty), unsized_info(bx, a, b, old_info))
}
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) if bx.cx().is_backend_scalar_pair(dst_layout) => {
assert_eq!(def_a, def_b);
let src_layout = bx.cx().layout_of(src_ty);
let dst_layout = bx.cx().layout_of(dst_ty);
if src_ty == dst_ty {
return (src, old_info.unwrap());
return OperandValue::Pair(src, old_info.unwrap());
}
let mut result = None;
for i in 0..src_layout.fields.count() {
Expand All @@ -226,16 +227,29 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(

let dst_f = dst_layout.field(bx.cx(), i);
assert_ne!(src_f.ty, dst_f.ty);
assert_eq!(result, None);
assert_matches!(result, None);
result = Some(unsize_ptr(bx, src, src_f.ty, dst_f.ty, old_info));
}
let (lldata, llextra) = result.unwrap();
let OperandValue::Pair(lldata, llextra) = result.unwrap()
else { bug!() };
let lldata_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 0, true);
let llextra_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 1, true);
// HACK(eddyb) have to bitcast pointers until LLVM removes pointee types.
(bx.bitcast(lldata, lldata_ty), bx.bitcast(llextra, llextra_ty))
OperandValue::Pair(bx.bitcast(lldata, lldata_ty), bx.bitcast(llextra, llextra_ty))
}
(&ty::Adt(def_a, substs_a), &ty::Adt(def_b, substs_b)) => {
assert_eq!(def_a, def_b);
let typed_metadata = bx.tcx().require_lang_item(LangItem::TypedMetadata, None);
assert_eq!(def_a.did(), typed_metadata);
if src_ty == dst_ty {
return OperandValue::Immediate(src);
}
let a = substs_a.type_at(0);
let b = substs_b.type_at(0);
assert_eq!(bx.cx().type_is_sized(a), old_info.is_none());
OperandValue::Immediate(unsized_info(bx, a, b, old_info))
}
_ => bug!("unsize_ptr: called on bad types"),
_ => bug!("unsize_ptr: called on bad types {:?} -> {:?}", src_ty, dst_ty),
}
}

Expand All @@ -250,12 +264,12 @@ pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
let dst_ty = dst.layout.ty;
match (src_ty.kind(), dst_ty.kind()) {
(&ty::Ref(..), &ty::Ref(..) | &ty::RawPtr(..)) | (&ty::RawPtr(..), &ty::RawPtr(..)) => {
let (base, info) = match bx.load_operand(src).val {
let val = match bx.load_operand(src).val {
OperandValue::Pair(base, info) => unsize_ptr(bx, base, src_ty, dst_ty, Some(info)),
OperandValue::Immediate(base) => unsize_ptr(bx, base, src_ty, dst_ty, None),
OperandValue::Ref(..) => bug!(),
};
OperandValue::Pair(base, info).store(bx, dst);
val.store(bx, dst);
}

(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(try_blocks)]
#![feature(let_else)]
Expand Down
5 changes: 1 addition & 4 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
operand.val
}
mir::CastKind::Pointer(PointerCast::Unsize) => {
assert!(bx.cx().is_backend_scalar_pair(cast));
let (lldata, llextra) = match operand.val {
OperandValue::Pair(lldata, llextra) => {
// unsize from a fat pointer -- this is a
Expand All @@ -240,9 +239,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
bug!("by-ref operand {:?} in `codegen_rvalue_operand`", operand);
}
};
let (lldata, llextra) =
base::unsize_ptr(&mut bx, lldata, operand.layout.ty, cast.ty, llextra);
OperandValue::Pair(lldata, llextra)
base::unsize_ptr(&mut bx, lldata, operand.layout.ty, cast.ty, llextra)
}
mir::CastKind::Pointer(PointerCast::MutToConstPointer)
| mir::CastKind::Misc
Expand Down
83 changes: 60 additions & 23 deletions compiler/rustc_const_eval/src/interpret/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::convert::TryFrom;

use rustc_apfloat::ieee::{Double, Single};
use rustc_apfloat::{Float, FloatConvert};
use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar};
use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar, ScalarMaybeUninit};
use rustc_middle::mir::CastKind;
use rustc_middle::ty::adjustment::PointerCast;
use rustc_middle::ty::layout::{IntegerExt, LayoutOf, TyAndLayout};
Expand Down Expand Up @@ -305,22 +305,37 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
source_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
) -> InterpResult<'tcx> {
// We *could* forward `data` without even checking that it is initialized, but for now,
// let's only allow casting properly initialized pointers.
let (data, old_meta) = match *self.read_immediate(src)? {
CAD97 marked this conversation as resolved.
Show resolved Hide resolved
// If the input ptr is thin, use `Uninit` for the old metadata.
// `unsize_just_metadata` knows how to handle that.
Immediate::Scalar(data) => (data.check_init()?, ScalarMaybeUninit::Uninit),
CAD97 marked this conversation as resolved.
Show resolved Hide resolved
Immediate::ScalarPair(data, meta) => (data.check_init()?, meta),
};

let new_meta = self.unsize_just_metadata(old_meta, source_ty, cast_ty)?;
self.write_immediate(Immediate::ScalarPair(data.into(), new_meta.into()), dest)
}

fn unsize_just_metadata(
CAD97 marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
src_meta: ScalarMaybeUninit<M::PointerTag>,
// The pointee types
source_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
) -> InterpResult<'tcx, Scalar<M::PointerTag>> {
// A<Struct> -> A<Trait> conversion
let (src_pointee_ty, dest_pointee_ty) =
self.tcx.struct_lockstep_tails_erasing_lifetimes(source_ty, cast_ty, self.param_env);

match (&src_pointee_ty.kind(), &dest_pointee_ty.kind()) {
Ok(match (&src_pointee_ty.kind(), &dest_pointee_ty.kind()) {
(&ty::Array(_, length), &ty::Slice(_)) => {
let ptr = self.read_immediate(src)?.to_scalar()?;
// u64 cast is from usize to u64, which is always good
let val =
Immediate::new_slice(ptr, length.eval_usize(*self.tcx, self.param_env), self);
self.write_immediate(val, dest)
Scalar::from_machine_usize(length.eval_usize(*self.tcx, self.param_env), self)
}
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
let val = self.read_immediate(src)?;
if data_a.principal_def_id() == data_b.principal_def_id() {
return self.write_immediate(*val, dest);
return src_meta.check_init();
}
// trait upcasting coercion
let vptr_entry_idx = self.tcx.vtable_trait_upcasting_coercion_new_vptr_slot((
Expand All @@ -330,27 +345,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {

if let Some(entry_idx) = vptr_entry_idx {
let entry_idx = u64::try_from(entry_idx).unwrap();
let (old_data, old_vptr) = val.to_scalar_pair()?;
let old_vptr = src_meta.check_init()?;
let old_vptr = self.scalar_to_ptr(old_vptr)?;
let new_vptr = self
.read_new_vtable_after_trait_upcasting_from_vtable(old_vptr, entry_idx)?;
self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest)
Scalar::from_maybe_pointer(new_vptr, self)
} else {
self.write_immediate(*val, dest)
src_meta.check_init()?
}
}
(_, &ty::Dynamic(ref data, _)) => {
// Initial cast from sized to dyn trait
let vtable = self.get_vtable(src_pointee_ty, data.principal())?;
let ptr = self.read_immediate(src)?.to_scalar()?;
let val = Immediate::new_dyn_trait(ptr, vtable, &*self.tcx);
self.write_immediate(val, dest)
Scalar::from_maybe_pointer(vtable, &*self.tcx)
}

_ => {
span_bug!(self.cur_span(), "invalid unsizing {:?} -> {:?}", src.layout.ty, cast_ty)
span_bug!(self.cur_span(), "invalid unsizing {:?} -> {:?}", source_ty, cast_ty)
}
}
})
}

fn unsize_into(
Expand All @@ -360,16 +373,40 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
dest: &PlaceTy<'tcx, M::PointerTag>,
) -> InterpResult<'tcx> {
trace!("Unsizing {:?} of type {} into {:?}", *src, src.layout.ty, cast_ty.ty);
match (&src.layout.ty.kind(), &cast_ty.ty.kind()) {
(&ty::Ref(_, s, _), &ty::Ref(_, c, _) | &ty::RawPtr(TypeAndMut { ty: c, .. }))
| (&ty::RawPtr(TypeAndMut { ty: s, .. }), &ty::RawPtr(TypeAndMut { ty: c, .. })) => {
let typed_metadata = self.tcx.lang_items().typed_metadata();
match (src.layout.ty.kind(), cast_ty.ty.kind()) {
(ty::Ref(_, s, _), ty::Ref(_, c, _) | ty::RawPtr(TypeAndMut { ty: c, .. }))
| (ty::RawPtr(TypeAndMut { ty: s, .. }), ty::RawPtr(TypeAndMut { ty: c, .. })) => {
self.unsize_into_ptr(src, dest, *s, *c)
}
(&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => {
(ty::Adt(def_a, _), ty::Adt(def_b, _)) if def_a.is_box() || def_b.is_box() => {
assert_eq!(def_a, def_b);

if !def_a.is_box() || !def_b.is_box() {
span_bug!(
self.cur_span(),
"invalid unsizing between {:?} -> {:?}",
src.layout.ty,
cast_ty.ty
);
}
self.unsize_into_ptr(src, dest, src.layout.ty.boxed_ty(), cast_ty.ty.boxed_ty())
CAD97 marked this conversation as resolved.
Show resolved Hide resolved
}
(ty::Adt(def_a, substs_a), ty::Adt(def_b, substs_b))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we want to pursue unsizing working directly with <T as Pointee>::Metadata, it would replace this arm checking for JustMetadata<T> with checking for the projection. The code should be nearly the same, since JustMetadata<T> is just lifting the projection to an Adt ty kind.

Copy link
Member

Choose a reason for hiding this comment

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

Projections tend to get normalized away though, so that does not sound like a great idea.

if def_a == def_b && Some(def_a.did()) == typed_metadata =>
{
// unsizing of TypedMetadata container
// Example: `TypedMetadata<T>` -> `TypedMetadata<dyn Trait>`
let a_pointee = substs_a.type_at(0);
let b_pointee = substs_b.type_at(0);
let src_field = self.operand_field(src, 0)?;
let src = self.read_immediate(&src_field)?.to_scalar_or_uninit();
let dst_field = self.place_field(dest, 0)?;
let new_meta = self.unsize_just_metadata(src, a_pointee, b_pointee)?;
self.write_scalar(new_meta, &dst_field)
}
(ty::Adt(def_a, _), ty::Adt(def_b, _)) if def_a == def_b => {
// unsizing of generic struct with pointer fields
// Example: `Arc<T>` -> `Arc<Trait>`
// Example: `Arc<T>` -> `Arc<dyn Trait>`
// here we need to increase the size of every &T thin ptr field to a fat ptr
for i in 0..src.layout.fields.count() {
let cast_ty_field = cast_ty.field(self, i);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ language_item_table! {
PointeeTrait, sym::pointee_trait, pointee_trait, Target::Trait, GenericRequirement::None;
Metadata, sym::metadata_type, metadata_type, Target::AssocTy, GenericRequirement::None;
DynMetadata, sym::dyn_metadata, dyn_metadata, Target::Struct, GenericRequirement::None;
TypedMetadata, sym::typed_metadata, typed_metadata, Target::Struct, GenericRequirement::Exact(1);

Freeze, sym::freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0);

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
impl_polarity => { table_direct }
impl_defaultness => { table_direct }
constness => { table_direct }
coerce_unsized_info => { table }
coerce_unsized_kind => { table }
mir_const_qualif => { table }
rendered_const => { table }
asyncness => { table_direct }
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1482,9 +1482,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
// if this is an impl of `CoerceUnsized`, create its
// "unsized info", else just store None
if Some(trait_ref.def_id) == self.tcx.lang_items().coerce_unsized_trait() {
let coerce_unsized_info =
self.tcx.at(item.span).coerce_unsized_info(def_id);
record!(self.tables.coerce_unsized_info[def_id] <- coerce_unsized_info);
let coerce_unsized_kind =
self.tcx.at(item.span).coerce_unsized_kind(def_id);
record!(self.tables.coerce_unsized_kind[def_id] <- coerce_unsized_kind);
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_metadata/src/rmeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ define_tables! {
is_intrinsic: Table<DefIndex, ()>,
impl_defaultness: Table<DefIndex, hir::Defaultness>,
// FIXME(eddyb) perhaps compute this on the fly if cheap enough?
coerce_unsized_info: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedInfo>>,
coerce_unsized_kind: Table<DefIndex, LazyValue<ty::adjustment::CoerceUnsizedKind>>,
mir_const_qualif: Table<DefIndex, LazyValue<mir::ConstQualifs>>,
rendered_const: Table<DefIndex, LazyValue<String>>,
asyncness: Table<DefIndex, hir::IsAsync>,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,8 +835,8 @@ rustc_queries! {
}

/// Caches `CoerceUnsized` kinds for impls on custom types.
query coerce_unsized_info(key: DefId) -> ty::adjustment::CoerceUnsizedInfo {
desc { |tcx| "computing CoerceUnsized info for `{}`", tcx.def_path_str(key) }
query coerce_unsized_kind(key: DefId) -> ty::adjustment::CoerceUnsizedKind {
desc { |tcx| "computing CoerceUnsized kind for `{}`", tcx.def_path_str(key) }
cache_on_disk_if { key.is_local() }
separate_provide_extern
}
Expand Down
21 changes: 8 additions & 13 deletions compiler/rustc_middle/src/ty/adjustment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,20 +177,15 @@ pub enum AutoBorrow<'tcx> {
/// Information for `CoerceUnsized` impls, storing information we
/// have computed about the coercion.
///
/// This struct can be obtained via the `coerce_impl_info` query.
/// This enum can be obtained via the `coerce_unsized_kind` query.
/// Demanding this struct also has the side-effect of reporting errors
/// for inappropriate impls.
#[derive(Clone, Copy, TyEncodable, TyDecodable, Debug, HashStable)]
pub struct CoerceUnsizedInfo {
/// If this is a "custom coerce" impl, then what kind of custom
/// coercion is it? This applies to impls of `CoerceUnsized` for
/// structs, primarily, where we store a bit of info about which
/// fields need to be coerced.
pub custom_kind: Option<CustomCoerceUnsized>,
}

#[derive(Clone, Copy, TyEncodable, TyDecodable, Debug, HashStable)]
pub enum CustomCoerceUnsized {
/// Records the index of the field being coerced.
#[derive(Clone, Copy, PartialEq, TyEncodable, TyDecodable, Debug, HashStable)]
pub enum CoerceUnsizedKind {
/// Coercion of a raw pointer or ref.
Ptr,
/// Coercion of a struct. Records the index of the field being coerced.
Struct(usize),
/// Coercion of the pointee metadata directly.
TypedMetadata,
}
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/ty/parameterized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ trivially_parameterized_over_tcx! {
ty::ReprOptions,
ty::TraitDef,
ty::Visibility,
ty::adjustment::CoerceUnsizedInfo,
ty::adjustment::CoerceUnsizedKind,
ty::fast_reject::SimplifiedTypeGen<DefId>,
rustc_ast::Attribute,
rustc_ast::MacArgs,
Expand Down
Loading