-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Remove dereferencing of Box from codegen #95576
Changes from all commits
3e9d3d9
cb41788
954fbe3
e0b0fb0
6cb38fb
1d1ff36
dff1f9f
6003c25
28ff0df
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 |
---|---|---|
|
@@ -118,22 +118,20 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { | |
} | ||
|
||
pub fn deref<Cx: LayoutTypeMethods<'tcx>>(self, cx: &Cx) -> PlaceRef<'tcx, V> { | ||
if self.layout.ty.is_box() { | ||
bug!("dereferencing {:?} in codegen", self.layout.ty); | ||
} | ||
|
||
let projected_ty = self | ||
.layout | ||
.ty | ||
.builtin_deref(true) | ||
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. Maybe enum BuiltinDerefMode {
/// `Box<T>`, `&T`, and optionally `*T`.
UserFacing {
/// Whether to also allow `*T`.
allow_unsafe: bool
},
/// `&T` and `*T`.
LowLevel,
} (Though |
||
.unwrap_or_else(|| bug!("deref of non-pointer {:?}", self)) | ||
.ty; | ||
|
||
let (llptr, llextra) = match self.val { | ||
OperandValue::Immediate(llptr) => (llptr, None), | ||
OperandValue::Pair(llptr, llextra) => { | ||
// if the box's allocator isn't a ZST, then "llextra" is actually the allocator | ||
if self.layout.ty.is_box() && !self.layout.field(cx, 1).is_zst() { | ||
(llptr, None) | ||
} else { | ||
(llptr, Some(llextra)) | ||
} | ||
} | ||
OperandValue::Pair(llptr, llextra) => (llptr, Some(llextra)), | ||
OperandValue::Ref(..) => bug!("Deref of by-Ref operand {:?}", self), | ||
}; | ||
let layout = cx.layout_of(projected_ty); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
//! This pass transforms derefs of Box into a deref of the pointer inside Box. | ||
//! | ||
//! Box is not actually a pointer so it is incorrect to dereference it directly. | ||
use crate::MirPass; | ||
use rustc_hir::def_id::DefId; | ||
use rustc_index::vec::Idx; | ||
use rustc_middle::mir::patch::MirPatch; | ||
use rustc_middle::mir::visit::MutVisitor; | ||
use rustc_middle::mir::*; | ||
use rustc_middle::ty::subst::Subst; | ||
use rustc_middle::ty::{Ty, TyCtxt}; | ||
|
||
/// Constructs the types used when accessing a Box's pointer | ||
pub fn build_ptr_tys<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
pointee: Ty<'tcx>, | ||
unique_did: DefId, | ||
nonnull_did: DefId, | ||
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) { | ||
let substs = tcx.intern_substs(&[pointee.into()]); | ||
let unique_ty = tcx.bound_type_of(unique_did).subst(tcx, substs); | ||
let nonnull_ty = tcx.bound_type_of(nonnull_did).subst(tcx, substs); | ||
let ptr_ty = tcx.mk_imm_ptr(pointee); | ||
|
||
(unique_ty, nonnull_ty, ptr_ty) | ||
} | ||
|
||
// Constructs the projection needed to access a Box's pointer | ||
pub fn build_projection<'tcx>( | ||
unique_ty: Ty<'tcx>, | ||
nonnull_ty: Ty<'tcx>, | ||
ptr_ty: Ty<'tcx>, | ||
) -> [PlaceElem<'tcx>; 3] { | ||
[ | ||
PlaceElem::Field(Field::new(0), unique_ty), | ||
PlaceElem::Field(Field::new(0), nonnull_ty), | ||
PlaceElem::Field(Field::new(0), ptr_ty), | ||
] | ||
} | ||
|
||
struct ElaborateBoxDerefVisitor<'tcx, 'a> { | ||
tcx: TyCtxt<'tcx>, | ||
unique_did: DefId, | ||
nonnull_did: DefId, | ||
local_decls: &'a mut LocalDecls<'tcx>, | ||
patch: MirPatch<'tcx>, | ||
} | ||
|
||
impl<'tcx, 'a> MutVisitor<'tcx> for ElaborateBoxDerefVisitor<'tcx, 'a> { | ||
fn tcx(&self) -> TyCtxt<'tcx> { | ||
self.tcx | ||
} | ||
|
||
fn visit_place( | ||
&mut self, | ||
place: &mut Place<'tcx>, | ||
context: visit::PlaceContext, | ||
location: Location, | ||
) { | ||
let tcx = self.tcx; | ||
|
||
let base_ty = self.local_decls[place.local].ty; | ||
|
||
// Derefer ensures that derefs are always the first projection | ||
if place.projection.first() == Some(&PlaceElem::Deref) && base_ty.is_box() { | ||
let source_info = self.local_decls[place.local].source_info; | ||
|
||
let (unique_ty, nonnull_ty, ptr_ty) = | ||
build_ptr_tys(tcx, base_ty.boxed_ty(), self.unique_did, self.nonnull_did); | ||
|
||
let ptr_local = self.patch.new_temp(ptr_ty, source_info.span); | ||
self.local_decls.push(LocalDecl::new(ptr_ty, source_info.span)); | ||
|
||
self.patch.add_statement(location, StatementKind::StorageLive(ptr_local)); | ||
|
||
self.patch.add_assign( | ||
location, | ||
Place::from(ptr_local), | ||
Rvalue::Use(Operand::Copy( | ||
Place::from(place.local) | ||
.project_deeper(&build_projection(unique_ty, nonnull_ty, ptr_ty), tcx), | ||
)), | ||
); | ||
|
||
place.local = ptr_local; | ||
|
||
self.patch.add_statement( | ||
Location { block: location.block, statement_index: location.statement_index + 1 }, | ||
StatementKind::StorageDead(ptr_local), | ||
); | ||
} | ||
|
||
self.super_place(place, context, location); | ||
} | ||
} | ||
|
||
pub struct ElaborateBoxDerefs; | ||
|
||
impl<'tcx> MirPass<'tcx> for ElaborateBoxDerefs { | ||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | ||
if let Some(def_id) = tcx.lang_items().owned_box() { | ||
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. not new in this PR, but we should probably start caching information like this by computing it when we create |
||
let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[0].did; | ||
|
||
let Some(nonnull_def) = tcx.type_of(unique_did).ty_adt_def() else { | ||
span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique") | ||
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. This should probably be using a lang item for Reported as an ICE by #98372; rustc_codegen_gcc's minicore defines |
||
}; | ||
|
||
let nonnull_did = nonnull_def.non_enum_variant().fields[0].did; | ||
|
||
let patch = MirPatch::new(body); | ||
|
||
let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut(); | ||
|
||
let mut visitor = | ||
ElaborateBoxDerefVisitor { tcx, unique_did, nonnull_did, local_decls, patch }; | ||
|
||
for (block, BasicBlockData { statements, terminator, .. }) in | ||
basic_blocks.iter_enumerated_mut() | ||
{ | ||
let mut index = 0; | ||
for statement in statements { | ||
let location = Location { block, statement_index: index }; | ||
visitor.visit_statement(statement, location); | ||
index += 1; | ||
} | ||
|
||
if let Some(terminator) = terminator | ||
&& !matches!(terminator.kind, TerminatorKind::Yield{..}) | ||
{ | ||
let location = Location { block, statement_index: index }; | ||
visitor.visit_terminator(terminator, location); | ||
} | ||
|
||
let location = Location { block, statement_index: index }; | ||
match terminator { | ||
// yielding into a box is handled when lowering generators | ||
Some(Terminator { kind: TerminatorKind::Yield { value, .. }, .. }) => { | ||
visitor.visit_operand(value, location); | ||
} | ||
Some(terminator) => { | ||
visitor.visit_terminator(terminator, location); | ||
} | ||
None => {} | ||
} | ||
} | ||
|
||
visitor.patch.apply(body); | ||
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. One thing that came up in the derefer PR is that derefs also happen in VarDebugInfo, but we don't have a good way to avoid multiple derefs there yet, but for your PR it may be useful to also unroll box derefs into multiple derefs 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. What code actually generates debug info that dereferences box? 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. I pushed some code to do this, but I have no idea if its actually correct because I don't know how to test it. 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. Not sure either. You could just panic the def id to find a function that exhibits it and then try to make a mir opt out of it? 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. Ooh, maybe it works via box patterns? |
||
|
||
for debug_info in body.var_debug_info.iter_mut() { | ||
if let VarDebugInfoContents::Place(place) = &mut debug_info.value { | ||
let mut new_projections: Option<Vec<_>> = None; | ||
let mut last_deref = 0; | ||
|
||
for (i, (base, elem)) in place.iter_projections().enumerate() { | ||
let base_ty = base.ty(&body.local_decls, tcx).ty; | ||
|
||
if elem == PlaceElem::Deref && base_ty.is_box() { | ||
let new_projections = new_projections.get_or_insert_default(); | ||
|
||
let (unique_ty, nonnull_ty, ptr_ty) = | ||
build_ptr_tys(tcx, base_ty.boxed_ty(), unique_did, nonnull_did); | ||
|
||
new_projections.extend_from_slice(&base.projection[last_deref..]); | ||
new_projections.extend_from_slice(&build_projection( | ||
unique_ty, nonnull_ty, ptr_ty, | ||
)); | ||
new_projections.push(PlaceElem::Deref); | ||
|
||
last_deref = i; | ||
} | ||
} | ||
|
||
if let Some(mut new_projections) = new_projections { | ||
new_projections.extend_from_slice(&place.projection[last_deref..]); | ||
place.projection = tcx.intern_place_elems(&new_projections); | ||
} | ||
} | ||
} | ||
} else { | ||
// box is not present, this pass doesn't need to do anything | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a similar assertion to Miri?
deref_operand
would probably be a good spot.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, does this mean you can remove the special cases I listed at #95453 (comment) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first 3 special cases have been removed. The debuginfo one probably shouldn't be changed because it could make debugging more annoying for no benefit. I'm currently investigating to see if the last one can be changed safely without breaking things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please remember to add a similar assertion to Miri. :)
Sorry for raising two points in the same thread. Sadly GitHub does not let one open multiple threads at the same line of code.