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

Make dyn* casts into a coercion, allow dyn* upcasting #101832

Merged
merged 4 commits into from
Oct 15, 2022
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
64 changes: 51 additions & 13 deletions compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use rustc_session::Session;
use rustc_span::symbol::sym;
use rustc_span::Symbol;
use rustc_span::{DebuggerVisualizerFile, DebuggerVisualizerType};
use rustc_target::abi::{Align, VariantIdx};
use rustc_target::abi::{Align, Size, VariantIdx};

use std::collections::BTreeSet;
use std::convert::TryFrom;
Expand Down Expand Up @@ -150,7 +150,12 @@ pub fn unsized_info<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
(&ty::Array(_, len), &ty::Slice(_)) => {
cx.const_usize(len.eval_usize(cx.tcx(), ty::ParamEnv::reveal_all()))
}
(&ty::Dynamic(ref data_a, ..), &ty::Dynamic(ref data_b, ..)) => {
(
&ty::Dynamic(ref data_a, _, src_dyn_kind),
&ty::Dynamic(ref data_b, _, target_dyn_kind),
) => {
assert_eq!(src_dyn_kind, target_dyn_kind);

let old_info =
old_info.expect("unsized_info: missing old info for trait upcasting coercion");
if data_a.principal_def_id() == data_b.principal_def_id() {
Expand All @@ -166,11 +171,7 @@ pub fn unsized_info<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
if let Some(entry_idx) = vptr_entry_idx {
let ptr_ty = cx.type_i8p();
let ptr_align = cx.tcx().data_layout.pointer_align.abi;
let vtable_ptr_ty = cx.scalar_pair_element_backend_type(
cx.layout_of(cx.tcx().mk_mut_ptr(target)),
1,
true,
);
let vtable_ptr_ty = vtable_ptr_ty(cx, target, target_dyn_kind);
let llvtable = bx.pointercast(old_info, bx.type_ptr_to(ptr_ty));
let gep = bx.inbounds_gep(
ptr_ty,
Expand All @@ -186,18 +187,32 @@ pub fn unsized_info<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
old_info
}
}
(_, &ty::Dynamic(ref data, ..)) => {
let vtable_ptr_ty = cx.scalar_pair_element_backend_type(
cx.layout_of(cx.tcx().mk_mut_ptr(target)),
1,
true,
);
(_, &ty::Dynamic(ref data, _, target_dyn_kind)) => {
let vtable_ptr_ty = vtable_ptr_ty(cx, target, target_dyn_kind);
cx.const_ptrcast(meth::get_vtable(cx, source, data.principal()), vtable_ptr_ty)
}
_ => bug!("unsized_info: invalid unsizing {:?} -> {:?}", source, target),
}
}

// Returns the vtable pointer type of a `dyn` or `dyn*` type
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.

fn vtable_ptr_ty<'tcx, Cx: CodegenMethods<'tcx>>(
cx: &Cx,
target: Ty<'tcx>,
kind: ty::DynKind,
) -> <Cx as BackendTypes>::Type {
cx.scalar_pair_element_backend_type(
cx.layout_of(match kind {
// vtable is the second field of `*mut dyn Trait`
ty::Dyn => cx.tcx().mk_mut_ptr(target),
// vtable is the second field of `dyn* Trait`
ty::DynStar => target,
eholk marked this conversation as resolved.
Show resolved Hide resolved
}),
1,
true,
)
}

/// Coerces `src` to `dst_ty`. `src_ty` must be a pointer.
pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
bx: &mut Bx,
Expand Down Expand Up @@ -247,6 +262,29 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
}
}

/// Coerces `src` to `dst_ty` which is guaranteed to be a `dyn*` type.
pub fn cast_to_dyn_star<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
bx: &mut Bx,
src: Bx::Value,
src_ty_and_layout: TyAndLayout<'tcx>,
dst_ty: Ty<'tcx>,
old_info: Option<Bx::Value>,
) -> (Bx::Value, Bx::Value) {
debug!("cast_to_dyn_star: {:?} => {:?}", src_ty_and_layout.ty, dst_ty);
assert!(
matches!(dst_ty.kind(), ty::Dynamic(_, _, ty::DynStar)),
"destination type must be a dyn*"
);
// FIXME(dyn-star): this is probably not the best way to check if this is
// a pointer, and really we should ensure that the value is a suitable
// pointer earlier in the compilation process.
let src = match src_ty_and_layout.pointee_info_at(bx.cx(), Size::ZERO) {
Some(_) => bx.ptrtoint(src, bx.cx().type_isize()),
None => bx.bitcast(src, bx.type_isize()),
};
(src, unsized_info(bx, src_ty_and_layout.ty, dst_ty, old_info))
}

/// Coerces `src`, which is a reference to a value of type `src_ty`,
/// to a value of type `dst_ty`, and stores the result in `dst`.
pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
Expand Down
27 changes: 6 additions & 21 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use super::{FunctionCx, LocalRef};

use crate::base;
use crate::common::{self, IntPredicate};
use crate::meth::get_vtable;
use crate::traits::*;
use crate::MemFlags;

Expand All @@ -14,7 +13,6 @@ use rustc_middle::ty::cast::{CastTy, IntTy};
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
use rustc_span::source_map::{Span, DUMMY_SP};
use rustc_target::abi::Size;

impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
#[instrument(level = "trace", skip(self, bx))]
Expand Down Expand Up @@ -274,27 +272,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
}
mir::CastKind::DynStar => {
let data = match operand.val {
let (lldata, llextra) = match operand.val {
OperandValue::Ref(_, _, _) => todo!(),
OperandValue::Immediate(v) => v,
OperandValue::Pair(_, _) => todo!(),
};
let trait_ref =
if let ty::Dynamic(data, _, ty::DynStar) = cast.ty.kind() {
data.principal()
} else {
bug!("Only valid to do a DynStar cast into a DynStar type")
};
let vtable = get_vtable(bx.cx(), source.ty(self.mir, bx.tcx()), trait_ref);
let vtable = bx.pointercast(vtable, bx.cx().type_ptr_to(bx.cx().type_isize()));
// FIXME(dyn-star): this is probably not the best way to check if this is
// a pointer, and really we should ensure that the value is a suitable
// pointer earlier in the compilation process.
let data = match operand.layout.pointee_info_at(bx.cx(), Size::ZERO) {
Some(_) => bx.ptrtoint(data, bx.cx().type_isize()),
None => data,
OperandValue::Immediate(v) => (v, None),
OperandValue::Pair(v, l) => (v, Some(l)),
};
OperandValue::Pair(data, vtable)
let (lldata, llextra) =
base::cast_to_dyn_star(&mut bx, lldata, operand.layout, cast.ty, llextra);
OperandValue::Pair(lldata, llextra)
}
mir::CastKind::Pointer(
PointerCast::MutToConstPointer | PointerCast::ArrayToPointer,
Expand Down
79 changes: 3 additions & 76 deletions compiler/rustc_hir_analysis/src/check/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ use crate::type_error_struct;
use hir::def_id::LOCAL_CRATE;
use rustc_errors::{struct_span_err, Applicability, DelayDm, DiagnosticBuilder, ErrorGuaranteed};
use rustc_hir as hir;
use rustc_infer::traits::{Obligation, ObligationCause, ObligationCauseCode};
use rustc_middle::mir::Mutability;
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::cast::{CastKind, CastTy};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, Binder, Ty, TypeAndMut, TypeVisitable, VariantDef};
use rustc_middle::ty::{self, Ty, TypeAndMut, TypeVisitable, VariantDef};
use rustc_session::lint;
use rustc_session::Session;
use rustc_span::symbol::sym;
Expand Down Expand Up @@ -204,76 +203,8 @@ fn make_invalid_casting_error<'a, 'tcx>(
)
}

pub enum CastCheckResult<'tcx> {
Ok,
Deferred(CastCheck<'tcx>),
Err(ErrorGuaranteed),
}

pub fn check_cast<'tcx>(
fcx: &FnCtxt<'_, 'tcx>,
expr: &'tcx hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
cast_span: Span,
span: Span,
) -> CastCheckResult<'tcx> {
if cast_ty.is_dyn_star() {
check_dyn_star_cast(fcx, expr, expr_ty, cast_ty)
} else {
match CastCheck::new(fcx, expr, expr_ty, cast_ty, cast_span, span) {
Ok(check) => CastCheckResult::Deferred(check),
Err(e) => CastCheckResult::Err(e),
}
}
}

fn check_dyn_star_cast<'tcx>(
fcx: &FnCtxt<'_, 'tcx>,
expr: &'tcx hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
) -> CastCheckResult<'tcx> {
// Find the bounds in the dyn*. For eaxmple, if we have
//
// let x = 22_usize as dyn* (Clone + Debug + 'static)
//
// this would return `existential_predicates = [?Self: Clone, ?Self: Debug]` and `region = 'static`.
let (existential_predicates, region) = match cast_ty.kind() {
ty::Dynamic(predicates, region, ty::DynStar) => (predicates, region),
_ => panic!("Invalid dyn* cast_ty"),
};

let cause = ObligationCause::new(
expr.span,
fcx.body_id,
// FIXME(dyn-star): Use a better obligation cause code
ObligationCauseCode::MiscObligation,
);

// For each existential predicate (e.g., `?Self: Clone`) substitute
// the type of the expression (e.g., `usize` in our example above)
// and then require that the resulting predicate (e.g., `usize: Clone`)
// holds (it does).
for existential_predicate in existential_predicates.iter() {
let predicate = existential_predicate.with_self_ty(fcx.tcx, expr_ty);
fcx.register_predicate(Obligation::new(cause.clone(), fcx.param_env, predicate));
}

// Enforce the region bound `'static` (e.g., `usize: 'static`, in our example).
fcx.register_predicate(Obligation::new(
cause,
fcx.param_env,
fcx.tcx.mk_predicate(Binder::dummy(ty::PredicateKind::TypeOutlives(
ty::OutlivesPredicate(expr_ty, *region),
))),
));

CastCheckResult::Ok
}

impl<'a, 'tcx> CastCheck<'tcx> {
fn new(
pub fn new(
fcx: &FnCtxt<'a, 'tcx>,
expr: &'tcx hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
Expand Down Expand Up @@ -934,11 +865,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {

(Int(_) | Float, Int(_) | Float) => Ok(CastKind::NumericCast),

// FIXME(dyn-star): this needs more conditions...
(_, DynStar) => Ok(CastKind::DynStarCast),

// FIXME(dyn-star): do we want to allow dyn* upcasting or other casts?
(DynStar, _) => Err(CastError::IllegalCast),
(_, DynStar) | (DynStar, _) => bug!("should be handled by `try_coerce`"),
}
}

Expand Down
60 changes: 60 additions & 0 deletions compiler/rustc_hir_analysis/src/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
ty::Ref(r_b, _, mutbl_b) => {
return self.coerce_borrowed_pointer(a, b, r_b, mutbl_b);
}
ty::Dynamic(predicates, region, ty::DynStar) if self.tcx.features().dyn_star => {
return self.coerce_dyn_star(a, b, predicates, region);
}
_ => {}
}

Expand Down Expand Up @@ -745,6 +748,63 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
Ok(coercion)
}

fn coerce_dyn_star(
&self,
a: Ty<'tcx>,
b: Ty<'tcx>,
predicates: &'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>,
b_region: ty::Region<'tcx>,
) -> CoerceResult<'tcx> {
if !self.tcx.features().dyn_star {
return Err(TypeError::Mismatch);
}

if let ty::Dynamic(a_data, _, _) = a.kind()
&& let ty::Dynamic(b_data, _, _) = b.kind()
{
if a_data.principal_def_id() == b_data.principal_def_id() {
return self.unify_and(a, b, |_| vec![]);
} else if !self.tcx().features().trait_upcasting {
let mut err = feature_err(
&self.tcx.sess.parse_sess,
sym::trait_upcasting,
self.cause.span,
&format!(
"cannot cast `{a}` to `{b}`, trait upcasting coercion is experimental"
),
);
err.emit();
}
}

// Check the obligations of the cast -- for example, when casting
// `usize` to `dyn* Clone + 'static`:
let obligations = predicates
.iter()
.map(|predicate| {
// For each existential predicate (e.g., `?Self: Clone`) substitute
// the type of the expression (e.g., `usize` in our example above)
// and then require that the resulting predicate (e.g., `usize: Clone`)
// holds (it does).
let predicate = predicate.with_self_ty(self.tcx, a);
Obligation::new(self.cause.clone(), self.param_env, predicate)
})
// Enforce the region bound (e.g., `usize: 'static`, in our example).
.chain([Obligation::new(
self.cause.clone(),
self.param_env,
self.tcx.mk_predicate(ty::Binder::dummy(ty::PredicateKind::TypeOutlives(
ty::OutlivesPredicate(a, b_region),
))),
)])
.collect();

Ok(InferOk {
value: (vec![Adjustment { kind: Adjust::DynStar, target: b }], b),
obligations,
})
}

fn coerce_from_safe_fn<F, G>(
&self,
a: Ty<'tcx>,
Expand Down
9 changes: 4 additions & 5 deletions compiler/rustc_hir_analysis/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! See `mod.rs` for more context on type checking in general.

use crate::astconv::AstConv as _;
use crate::check::cast::{self, CastCheckResult};
use crate::check::cast;
use crate::check::coercion::CoerceMany;
use crate::check::fatally_break_rust;
use crate::check::method::SelfSource;
Expand Down Expand Up @@ -1270,17 +1270,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else {
// Defer other checks until we're done type checking.
let mut deferred_cast_checks = self.deferred_cast_checks.borrow_mut();
match cast::check_cast(self, e, t_expr, t_cast, t.span, expr.span) {
CastCheckResult::Ok => t_cast,
CastCheckResult::Deferred(cast_check) => {
match cast::CastCheck::new(self, e, t_expr, t_cast, t.span, expr.span) {
Ok(cast_check) => {
debug!(
"check_expr_cast: deferring cast from {:?} to {:?}: {:?}",
t_cast, t_expr, cast_check,
);
deferred_cast_checks.push(cast_check);
t_cast
}
CastCheckResult::Err(ErrorGuaranteed { .. }) => self.tcx.ty_error(),
Err(_) => self.tcx.ty_error(),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_hir_analysis/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,9 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
for adjustment in adjustments {
debug!("walk_adjustment expr={:?} adj={:?}", expr, adjustment);
match adjustment.kind {
adjustment::Adjust::NeverToAny | adjustment::Adjust::Pointer(_) => {
adjustment::Adjust::NeverToAny
| adjustment::Adjust::Pointer(_)
| adjustment::Adjust::DynStar => {
// Creating a closure/fn-pointer or unsizing consumes
// the input and stores it into the resulting rvalue.
self.delegate_consume(&place_with_id, place_with_id.hir_id);
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir_analysis/src/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {

adjustment::Adjust::NeverToAny
| adjustment::Adjust::Pointer(_)
| adjustment::Adjust::Borrow(_) => {
| adjustment::Adjust::Borrow(_)
| adjustment::Adjust::DynStar => {
// Result is an rvalue.
Ok(self.cat_rvalue(expr.hir_id, expr.span, target))
}
Expand Down
Loading