Skip to content

Commit 23a6bda

Browse files
Rollup merge of rust-lang#158100 - BorrowSanitizer:codegen-emit-retag-4, r=saethlin
Emit retags in codegen to support BorrowSanitizer (part 4) Tracking issue: rust-lang#154760 [Zulip Thread](https://rust-lang.zulipchat.com/#narrow/channel/131828-t-compiler/topic/Staging.20for.20emitting.20retags.20in.20codegen/with/593004012) This is one of several PRs that add experimental support for emitting retags as function calls in codegen. This PR emits our retag intrinsics based on the structure of a `RetagPlan`. The feature works end-to-end now! But, it has two limitations: * We do not track interior mutable or pinned ranges precisely. This will come in the next PR, which should be the last one that we need to complete this feature (excluding documentation and compile tests). * We recurse into `repr(simd)` types when building a `RetagPlan`, but we do not emit retags for their fields. We would need `BuilderMethods::insert_element`, which isn't available. I'm not sure what the best workaround would be for this. Related: rust-lang#157825, rust-lang#156210, rust-lang#156208 Cc: @RalfJung r? @saethlin
2 parents 7351d69 + c268765 commit 23a6bda

2 files changed

Lines changed: 234 additions & 21 deletions

File tree

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,34 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
718718
OperandRefBuilder { val, layout }
719719
}
720720

721+
/// Creates an initialized builder for updating an existing `operand`.
722+
///
723+
/// ICEs for [`BackendRepr::Memory`] types (other than ZSTs), which use
724+
/// which use [`OperandValue::Ref`]. In this case, updates should be
725+
/// performed by writing into the place
726+
pub(super) fn from_existing(operand: OperandRef<'tcx, V>) -> Self {
727+
let layout = operand.layout;
728+
let val = match (operand.val, layout.backend_repr) {
729+
(OperandValue::ZeroSized, _) => OperandValueBuilder::ZeroSized,
730+
(OperandValue::Immediate(v), BackendRepr::Scalar(_)) => {
731+
OperandValueBuilder::Immediate(Either::Left(v))
732+
}
733+
(OperandValue::Immediate(v), BackendRepr::SimdVector { .. }) => {
734+
OperandValueBuilder::Vector(Either::Left(v))
735+
}
736+
(OperandValue::Pair(a, b), BackendRepr::ScalarPair(_, _)) => {
737+
OperandValueBuilder::Pair(Either::Left(a), Either::Left(b))
738+
}
739+
(_, BackendRepr::Memory { .. }) => {
740+
bug!("Cannot use non-ZST Memory-ABI type in operand builder: {layout:?}");
741+
}
742+
_ => {
743+
bug!("Operand cannot be used with `from_existing`: {operand:?}")
744+
}
745+
};
746+
OperandRefBuilder { val, layout }
747+
}
748+
721749
pub(super) fn insert_field<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
722750
&mut self,
723751
bx: &mut Bx,
@@ -829,6 +857,27 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
829857
}
830858
}
831859

860+
/// Replaces the current immediate value at the offset `offset`
861+
/// with the value `imm`. A value must already be present.
862+
///
863+
/// This is used along with [`Self::from_existing`] to perform in-place updates
864+
/// of any operand.
865+
pub(super) fn update_imm(&mut self, offset: Size, imm: V) {
866+
let is_zero_offset = offset == Size::ZERO;
867+
match &mut self.val {
868+
OperandValueBuilder::Immediate(val @ Either::Left(_)) if is_zero_offset => {
869+
*val = Either::Left(imm);
870+
}
871+
OperandValueBuilder::Pair(fst @ Either::Left(_), _) if is_zero_offset => {
872+
*fst = Either::Left(imm);
873+
}
874+
OperandValueBuilder::Pair(_, snd @ Either::Left(_)) if !is_zero_offset => {
875+
*snd = Either::Left(imm);
876+
}
877+
_ => bug!("Tried to update {imm:?} at offset {offset:?} of {self:?}"),
878+
}
879+
}
880+
832881
/// After having set all necessary fields, this converts the builder back
833882
/// to the normal `OperandRef`.
834883
///

compiler/rustc_codegen_ssa/src/mir/retag.rs

Lines changed: 185 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
//! of an assignment. The first step to retagging is to generate a [`RetagPlan`], which
55
//! describes which pointers within the place or operand can be retagged.
66
7-
#![allow(unused)]
8-
use rustc_abi::{BackendRepr, FieldIdx, FieldsShape, VariantIdx, Variants};
7+
use rustc_abi::{FieldIdx, FieldsShape, Size, VariantIdx, Variants};
98
use rustc_ast::Mutability;
109
use rustc_data_structures::fx::FxIndexMap;
1110
use rustc_middle::mir::{Rvalue, WithRetag};
1211
use rustc_middle::ty;
1312
use rustc_middle::ty::layout::TyAndLayout;
1413

15-
use crate::RetagInfo;
1614
use crate::mir::FunctionCx;
17-
use crate::mir::operand::OperandRef;
15+
use crate::mir::operand::{OperandRef, OperandRefBuilder, OperandValue};
1816
use crate::mir::place::PlaceRef;
19-
use crate::traits::BuilderMethods;
17+
use crate::traits::{
18+
BaseTypeCodegenMethods, BuilderMethods, ConstCodegenMethods, LayoutTypeCodegenMethods,
19+
};
20+
use crate::{RetagFlags, RetagInfo};
2021

2122
pub(crate) fn rvalue_needs_retag(rvalue: &Rvalue<'_>) -> bool {
2223
// `Ref` has its own internal retagging
@@ -58,12 +59,6 @@ impl<'a, 'tcx, V> RetagPlan<V> {
5859
if layout.is_sized() && layout.size < bx.tcx().data_layout.pointer_size() {
5960
return None;
6061
}
61-
// SIMD vectors may only contain raw pointers, integers, and floating point values,
62-
// which do not need to be retagged.
63-
if matches!(layout.backend_repr, BackendRepr::SimdVector { .. }) {
64-
return None;
65-
}
66-
6762
// Check the type of this value to see what to do with it (retag, or recurse).
6863
match layout.ty.kind() {
6964
&ty::Ref(_, pointee, mt) => {
@@ -168,32 +163,201 @@ impl<'a, 'tcx, V> RetagPlan<V> {
168163
/// to types that are entirely covered by `UnsafePinned`, for which retags
169164
/// are a no-op.
170165
fn emit_retag<Bx: BuilderMethods<'a, 'tcx>>(
171-
_bx: &mut Bx,
172-
_pointee_layout: TyAndLayout<'tcx>,
173-
_ptr_kind: Option<Mutability>,
174-
_is_fn_entry: bool,
166+
bx: &mut Bx,
167+
pointee_layout: TyAndLayout<'tcx>,
168+
ptr_kind: Option<Mutability>,
169+
is_fn_entry: bool,
175170
) -> Option<RetagPlan<Bx::Value>> {
176-
None
171+
let tcx = bx.tcx();
172+
173+
let pointee_ty = pointee_layout.ty;
174+
175+
let is_mutable = matches!(ptr_kind, Some(Mutability::Mut) | None);
176+
let is_unpin = pointee_ty.is_unpin(tcx, bx.typing_env());
177+
let is_freeze = pointee_ty.is_freeze(tcx, bx.typing_env());
178+
let is_box = ptr_kind.is_none();
179+
180+
// `&mut !Unpin` is not protected
181+
let is_protected = is_fn_entry && (!is_mutable || is_unpin);
182+
183+
if is_mutable && !is_unpin {
184+
return None;
185+
}
186+
187+
let im_layout = bx.const_null(bx.type_ptr());
188+
let pin_layout = bx.const_null(bx.type_ptr());
189+
190+
let mut flags = RetagFlags::empty();
191+
flags.set(RetagFlags::IS_PROTECTED, is_protected);
192+
flags.set(RetagFlags::IS_MUTABLE, is_mutable);
193+
flags.set(RetagFlags::IS_FREEZE, is_freeze);
194+
flags.set(RetagFlags::IS_BOX, is_box);
195+
196+
Some(RetagPlan::EmitRetag(RetagInfo {
197+
size: pointee_layout.size,
198+
im_layout,
199+
pin_layout,
200+
flags,
201+
}))
177202
}
178203
}
179204

180205
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
181206
/// Retags the pointers within an [`OperandRef`].
182207
pub(crate) fn codegen_retag_operand(
183208
&mut self,
184-
_bx: &mut Bx,
209+
bx: &mut Bx,
185210
operand: OperandRef<'tcx, Bx::Value>,
186-
_is_fn_entry: bool,
211+
is_fn_entry: bool,
187212
) -> OperandRef<'tcx, Bx::Value> {
213+
if let OperandValue::Ref(place_ref) = operand.val {
214+
let place_ref = place_ref.with_type(operand.layout);
215+
self.codegen_retag_place(bx, place_ref, is_fn_entry);
216+
} else if let Some(plan) = RetagPlan::<Bx::Value>::build(bx, operand.layout, is_fn_entry) {
217+
let mut builder = OperandRefBuilder::from_existing(operand);
218+
self.retag_operand(bx, &plan, operand, &mut builder, Size::ZERO);
219+
return builder.build(bx.cx());
220+
}
188221
operand
189222
}
190223

191224
/// Retags the pointers within a [`PlaceRef`].
192225
pub(crate) fn codegen_retag_place(
193226
&mut self,
194-
_bx: &mut Bx,
195-
_place_ref: PlaceRef<'tcx, Bx::Value>,
196-
_is_fn_entry: bool,
227+
bx: &mut Bx,
228+
place_ref: PlaceRef<'tcx, Bx::Value>,
229+
is_fn_entry: bool,
197230
) {
231+
if let Some(plan) = RetagPlan::<Bx::Value>::build(bx, place_ref.layout, is_fn_entry) {
232+
self.retag_place(bx, &plan, place_ref);
233+
}
234+
}
235+
236+
fn retag_operand(
237+
&mut self,
238+
bx: &mut Bx,
239+
plan: &RetagPlan<Bx::Value>,
240+
curr_operand: OperandRef<'tcx, Bx::Value>,
241+
builder: &mut OperandRefBuilder<'tcx, Bx::Value>,
242+
offset: Size,
243+
) {
244+
match plan {
245+
RetagPlan::EmitRetag(info) => {
246+
let (pointer, _) = curr_operand.val.pointer_parts();
247+
let retagged_pointer = bx.retag_reg(pointer, info);
248+
builder.update_imm(offset, retagged_pointer);
249+
}
250+
RetagPlan::Recurse { field_plans, variant_plans } => {
251+
let layout = curr_operand.layout;
252+
for (ix, plan) in field_plans {
253+
let inner_offset = layout.fields.offset(ix.as_usize());
254+
let field_offset = offset + inner_offset;
255+
256+
let field_layout = curr_operand.layout.field(bx, ix.index());
257+
// Part of https://github.com/rust-lang/compiler-team/issues/838
258+
if !bx.is_backend_ref(curr_operand.layout) && bx.is_backend_ref(field_layout) {
259+
// FIXME: support vector types, requires insert_element as part of cg-ssa
260+
} else {
261+
let field_operand = curr_operand.extract_field(self, bx, ix.as_usize());
262+
self.retag_operand(bx, &plan, field_operand, builder, field_offset);
263+
}
264+
}
265+
266+
if !variant_plans.is_empty() {
267+
let discr_ty = layout.ty.discriminant_ty(bx.tcx());
268+
let discr_val = curr_operand.codegen_get_discr(self, bx, discr_ty);
269+
270+
if let Some(val) = bx.const_to_opt_u128(discr_val, false) {
271+
let ix = VariantIdx::from_usize(val as usize);
272+
if let Some(plan) = variant_plans.get(&ix) {
273+
let mut variant_op = curr_operand;
274+
variant_op.layout = curr_operand.layout.for_variant(bx, ix);
275+
276+
self.retag_operand(bx, plan, variant_op, builder, offset);
277+
}
278+
} else {
279+
// We create a temporary place to store the operand, because its value will differ
280+
// depending on the variant that we have.
281+
let scratch = PlaceRef::alloca(bx, curr_operand.layout);
282+
scratch.storage_live(bx);
283+
curr_operand.store_with_annotation(bx, scratch);
284+
285+
// We retag the contents of the place
286+
self.retag_variants(bx, scratch, discr_val, variant_plans);
287+
288+
// Afterward, we load the now-updated operand and end the lifetime of the place.
289+
let updated_op = bx.load_operand(scratch);
290+
scratch.storage_dead(bx);
291+
292+
match updated_op.val {
293+
OperandValue::ZeroSized | OperandValue::Ref(_) => {}
294+
OperandValue::Immediate(imm) => builder.update_imm(offset, imm),
295+
OperandValue::Pair(fst, snd) => {
296+
builder.update_imm(offset, fst);
297+
builder.update_imm(offset + Size::from_bytes(1), snd)
298+
}
299+
}
300+
}
301+
}
302+
}
303+
}
304+
}
305+
306+
fn retag_place(
307+
&mut self,
308+
bx: &mut Bx,
309+
plan: &RetagPlan<Bx::Value>,
310+
place: PlaceRef<'tcx, Bx::Value>,
311+
) {
312+
match plan {
313+
RetagPlan::EmitRetag(info) => {
314+
bx.retag_mem(place.val.llval, info);
315+
}
316+
RetagPlan::Recurse { field_plans, variant_plans } => {
317+
for (ix, plan) in field_plans {
318+
let field_place = place.project_field(bx, ix.as_usize());
319+
self.retag_place(bx, &plan, field_place);
320+
}
321+
if !variant_plans.is_empty() {
322+
let operand = bx.load_operand(place);
323+
let discr_ty = place.layout.ty.discriminant_ty(bx.tcx());
324+
let discr_val = operand.codegen_get_discr(self, bx, discr_ty);
325+
self.retag_variants(bx, place, discr_val, variant_plans);
326+
}
327+
}
328+
}
329+
}
330+
331+
/// Retags each variant of a [`PlaceRef`] with the given discriminant.
332+
fn retag_variants(
333+
&mut self,
334+
bx: &mut Bx,
335+
place: PlaceRef<'tcx, Bx::Value>,
336+
discr: Bx::Value,
337+
variant_plans: &FxIndexMap<VariantIdx, RetagPlan<Bx::Value>>,
338+
) {
339+
let layout = place.layout;
340+
341+
let root_block = bx.llbb();
342+
let mut variant_blocks = Vec::with_capacity(variant_plans.len());
343+
let join_block = bx.append_sibling_block("retag_join");
344+
345+
for (ix, plan) in variant_plans {
346+
let variant_discr = layout.ty.discriminant_for_variant(bx.tcx(), *ix);
347+
let variant_discr_val = variant_discr.expect("Invalid variant index.").val;
348+
349+
let variant_block = bx.append_sibling_block("retag_variant");
350+
bx.switch_to_block(variant_block);
351+
352+
let variant_place = place.project_downcast(bx, *ix);
353+
self.retag_place(bx, plan, variant_place);
354+
355+
variant_blocks.push((variant_discr_val, variant_block));
356+
bx.br(join_block);
357+
}
358+
359+
bx.switch_to_block(root_block);
360+
bx.switch(discr, join_block, variant_blocks.into_iter());
361+
bx.switch_to_block(join_block);
198362
}
199363
}

0 commit comments

Comments
 (0)