Skip to content

Commit 7d46308

Browse files
committed
Auto merge of #138582 - scottmcm:option-ssa-2, r=<try>
Don't require `alloca`s for consuming simple enums Well, 4 months later I'm finally back to this. For example, if you pass an `Option<u32>` to a function, don't immediately write it to an `alloca` then read it again.
2 parents 9748d87 + a04ce83 commit 7d46308

File tree

15 files changed

+361
-167
lines changed

15 files changed

+361
-167
lines changed

compiler/rustc_abi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1821,7 +1821,7 @@ pub enum Variants<FieldIdx: Idx, VariantIdx: Idx> {
18211821

18221822
/// Single enum variants, structs/tuples, unions, and all non-ADTs.
18231823
Single {
1824-
/// Always `0` for types that cannot have multiple variants.
1824+
/// Always [`FIRST_VARIANT`] for types that cannot have multiple variants.
18251825
index: VariantIdx,
18261826
},
18271827

compiler/rustc_codegen_ssa/src/mir/analyze.rs

Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_index::bit_set::DenseBitSet;
66
use rustc_index::{IndexSlice, IndexVec};
77
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
88
use rustc_middle::mir::{self, DefLocation, Location, TerminatorKind, traversal};
9-
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
9+
use rustc_middle::ty::layout::LayoutOf;
1010
use rustc_middle::{bug, span_bug};
1111
use tracing::debug;
1212

@@ -99,63 +99,46 @@ impl<'a, 'b, 'tcx, Bx: BuilderMethods<'b, 'tcx>> LocalAnalyzer<'a, 'b, 'tcx, Bx>
9999
context: PlaceContext,
100100
___location: Location,
101101
) {
102-
let cx = self.fx.cx;
103-
104-
if let Some((place_base, elem)) = place_ref.last_projection() {
105-
let mut base_context = if context.is_mutating_use() {
106-
PlaceContext::MutatingUse(MutatingUseContext::Projection)
107-
} else {
108-
PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection)
109-
};
110-
111-
// Allow uses of projections that are ZSTs or from scalar fields.
112-
let is_consume = matches!(
113-
context,
114-
PlaceContext::NonMutatingUse(
115-
NonMutatingUseContext::Copy | NonMutatingUseContext::Move,
116-
)
117-
);
118-
if is_consume {
119-
let base_ty = place_base.ty(self.fx.mir, cx.tcx());
120-
let base_ty = self.fx.monomorphize(base_ty);
121-
122-
// ZSTs don't require any actual memory access.
123-
let elem_ty = base_ty.projection_ty(cx.tcx(), self.fx.monomorphize(elem)).ty;
124-
let span = self.fx.mir.local_decls[place_ref.local].source_info.span;
125-
if cx.spanned_layout_of(elem_ty, span).is_zst() {
126-
return;
102+
if !place_ref.projection.is_empty() {
103+
const COPY_CONTEXT: PlaceContext =
104+
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
105+
106+
// `PlaceElem::Index` is the only variant that can mention other `Local`s,
107+
// so check for those up-front before any potential short-circuits.
108+
for elem in place_ref.projection {
109+
if let mir::PlaceElem::Index(index_local) = *elem {
110+
self.visit_local(index_local, COPY_CONTEXT, ___location);
127111
}
112+
}
128113

129-
if let mir::ProjectionElem::Field(..) = elem {
130-
let layout = cx.spanned_layout_of(base_ty.ty, span);
131-
if cx.is_backend_immediate(layout) || cx.is_backend_scalar_pair(layout) {
132-
// Recurse with the same context, instead of `Projection`,
133-
// potentially stopping at non-operand projections,
134-
// which would trigger `not_ssa` on locals.
135-
base_context = context;
136-
}
137-
}
114+
// If our local is already memory, nothing can make it *more* memory
115+
// so we don't need to bother checking the projections further.
116+
if self.locals[place_ref.local] == LocalKind::Memory {
117+
return;
138118
}
139119

140-
if let mir::ProjectionElem::Deref = elem {
141-
// Deref projections typically only read the pointer.
142-
base_context = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
120+
if place_ref.is_indirect_first_projection() {
121+
// If this starts with a `Deref`, we only need to record a read of the
122+
// pointer being dereferenced, as all the subsequent projections are
123+
// working on a place which is always supported. (And because we're
124+
// looking at codegen MIR, it can only happen as the first projection.)
125+
self.visit_local(place_ref.local, COPY_CONTEXT, ___location);
126+
return;
143127
}
144128

145-
self.process_place(&place_base, base_context, ___location);
146-
// HACK(eddyb) this emulates the old `visit_projection_elem`, this
147-
// entire `visit_place`-like `process_place` method should be rewritten,
148-
// now that we have moved to the "slice of projections" representation.
149-
if let mir::ProjectionElem::Index(local) = elem {
150-
self.visit_local(
151-
local,
152-
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy),
153-
___location,
154-
);
129+
if context.is_mutating_use() {
130+
// If it's a mutating use it doesn't matter what the projections are,
131+
// if there are *any* then we need a place to write. (For example,
132+
// `_1 = Foo()` works in SSA but `_2.0 = Foo()` does not.)
133+
let mut_projection = PlaceContext::MutatingUse(MutatingUseContext::Projection);
134+
self.visit_local(place_ref.local, mut_projection, ___location);
135+
return;
155136
}
156-
} else {
157-
self.visit_local(place_ref.local, context, ___location);
158137
}
138+
139+
// Even with supported projections, we still need to have `visit_local`
140+
// check for things that can't be done in SSA (like `SharedBorrow`).
141+
self.visit_local(place_ref.local, context, ___location);
159142
}
160143
}
161144

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::cmp;
22

3-
use rustc_abi::{Align, BackendRepr, ExternAbi, HasDataLayout, Reg, Size, WrappingRange};
3+
use rustc_abi::{Align, BackendRepr, ExternAbi, FieldIdx, HasDataLayout, Reg, Size, WrappingRange};
44
use rustc_ast as ast;
55
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
66
use rustc_data_structures::packed::Pu128;
@@ -1038,7 +1038,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10381038
let (idx, _) = op.layout.non_1zst_field(bx).expect(
10391039
"not exactly one non-1-ZST field in a `DispatchFromDyn` type",
10401040
);
1041-
op = op.extract_field(self, bx, idx.as_usize());
1041+
op = op.extract_field(self, bx, None, idx);
10421042
}
10431043

10441044
// Now that we have `*dyn Trait` or `&dyn Trait`, split it up into its
@@ -1598,7 +1598,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
15981598
} else {
15991599
// If the tuple is immediate, the elements are as well.
16001600
for i in 0..tuple.layout.fields.count() {
1601-
let op = tuple.extract_field(self, bx, i);
1601+
let op = tuple.extract_field(self, bx, None, FieldIdx::from_usize(i));
16021602
self.codegen_argument(bx, op, llargs, &args[i], lifetime_ends_after_call);
16031603
}
16041604
}

compiler/rustc_codegen_ssa/src/mir/locals.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use tracing::{debug, warn};
1212
use crate::mir::{FunctionCx, LocalRef};
1313
use crate::traits::BuilderMethods;
1414

15+
#[derive(Debug)]
1516
pub(super) struct Locals<'tcx, V> {
1617
values: IndexVec<mir::Local, LocalRef<'tcx, V>>,
1718
}

compiler/rustc_codegen_ssa/src/mir/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
136136
}
137137
}
138138

139+
#[derive(Debug)]
139140
enum LocalRef<'tcx, V> {
140141
Place(PlaceRef<'tcx, V>),
141142
/// `UnsizedPlace(p)`: `p` itself is a thin pointer (indirect place).

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ pub struct OperandRef<'tcx, V> {
126126
pub layout: TyAndLayout<'tcx>,
127127
}
128128

129-
impl<V: CodegenObject> fmt::Debug for OperandRef<'_, V> {
129+
impl<V: fmt::Debug> fmt::Debug for OperandRef<'_, V> {
130130
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131131
write!(f, "OperandRef({:?} @ {:?})", self.val, self.layout)
132132
}
@@ -319,16 +319,40 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
319319
OperandRef { val, layout }
320320
}
321321

322+
/// Extracts field `field_idx` from `self`, after downcasting to
323+
/// `downcast_variant` if it's specified.
324+
///
325+
/// Reading from things like tuples or structs, which are always single-variant,
326+
/// don't need to pass a downcast variant since downcasting them to
327+
/// `FIRST_VARIANT` doesn't actually change anything.
328+
/// Things like enums and coroutines, though, must pass the variant from which
329+
/// they want to read unless they're specifically reading the tag field
330+
/// (which is the index at the type level, not inside one of the variants).
322331
pub(crate) fn extract_field<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
323332
&self,
324333
fx: &mut FunctionCx<'a, 'tcx, Bx>,
325334
bx: &mut Bx,
326-
i: usize,
335+
downcast_variant: Option<VariantIdx>,
336+
field_idx: FieldIdx,
327337
) -> Self {
328-
let field = self.layout.field(bx.cx(), i);
329-
let offset = self.layout.fields.offset(i);
338+
let layout = if let Some(vidx) = downcast_variant {
339+
self.layout.for_variant(bx.cx(), vidx)
340+
} else {
341+
debug_assert!(
342+
match self.layout.variants {
343+
Variants::Empty => true,
344+
Variants::Single { index } => index == FIRST_VARIANT,
345+
Variants::Multiple { tag_field, .. } => tag_field == field_idx,
346+
},
347+
"Should have specified a variant to read field {field_idx:?} of {self:?}",
348+
);
349+
self.layout
350+
};
351+
352+
let field = layout.field(bx.cx(), field_idx.as_usize());
353+
let offset = layout.fields.offset(field_idx.as_usize());
330354

331-
if !bx.is_backend_ref(self.layout) && bx.is_backend_ref(field) {
355+
if !bx.is_backend_ref(layout) && bx.is_backend_ref(field) {
332356
// Part of https://github.com/rust-lang/compiler-team/issues/838
333357
span_bug!(
334358
fx.mir.span,
@@ -338,27 +362,35 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
338362

339363
let val = if field.is_zst() {
340364
OperandValue::ZeroSized
341-
} else if let BackendRepr::SimdVector { .. } = self.layout.backend_repr {
365+
} else if let BackendRepr::SimdVector { .. } = layout.backend_repr {
342366
// codegen_transmute_operand doesn't support SIMD, but since the previous
343367
// check handled ZSTs, the only possible field access into something SIMD
344368
// is to the `non_1zst_field` that's the same SIMD. (Other things, even
345369
// just padding, would change the wrapper's representation type.)
346-
assert_eq!(field.size, self.layout.size);
370+
assert_eq!(field.size, layout.size);
347371
self.val
348-
} else if field.size == self.layout.size {
349-
assert_eq!(offset.bytes(), 0);
350-
fx.codegen_transmute_operand(bx, *self, field)
372+
} else if field.size == layout.size {
373+
debug_assert_eq!(offset.bytes(), 0);
374+
if downcast_variant.is_some() || self.layout.ty.is_union() {
375+
fx.codegen_transmute_operand(bx, *self, field)
376+
} else {
377+
self.val
378+
}
351379
} else {
352-
let (in_scalar, imm) = match (self.val, self.layout.backend_repr) {
380+
let (in_scalar, imm) = match (self.val, layout.backend_repr) {
353381
// Extract a scalar component from a pair.
354382
(OperandValue::Pair(a_llval, b_llval), BackendRepr::ScalarPair(a, b)) => {
355-
if offset.bytes() == 0 {
383+
// This needs to look at `offset`, rather than `i`, because
384+
// for a type like `Option<u32>`, the first thing in the pair
385+
// is the tag, so `(_2 as Some).0` needs to read the *second*
386+
// thing in the pair despite it being "field zero".
387+
if offset == Size::ZERO {
356388
assert_eq!(field.size, a.size(bx.cx()));
357-
(Some(a), a_llval)
389+
(a, a_llval)
358390
} else {
359391
assert_eq!(offset, a.size(bx.cx()).align_to(b.align(bx.cx()).abi));
360392
assert_eq!(field.size, b.size(bx.cx()));
361-
(Some(b), b_llval)
393+
(b, b_llval)
362394
}
363395
}
364396

@@ -367,30 +399,18 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
367399
}
368400
};
369401
OperandValue::Immediate(match field.backend_repr {
370-
BackendRepr::SimdVector { .. } => imm,
371-
BackendRepr::Scalar(out_scalar) => {
372-
let Some(in_scalar) = in_scalar else {
373-
span_bug!(
374-
fx.mir.span,
375-
"OperandRef::extract_field({:?}): missing input scalar for output scalar",
376-
self
377-
)
378-
};
379-
if in_scalar != out_scalar {
380-
// If the backend and backend_immediate types might differ,
381-
// flip back to the backend type then to the new immediate.
382-
// This avoids nop truncations, but still handles things like
383-
// Bools in union fields needs to be truncated.
384-
let backend = bx.from_immediate(imm);
385-
bx.to_immediate_scalar(backend, out_scalar)
386-
} else {
387-
imm
388-
}
402+
BackendRepr::Scalar(out_scalar) if downcast_variant.is_some() => {
403+
// For a type like `Result<usize, &u32>` the layout is `Pair(i64, ptr)`.
404+
// But if we're reading the `Ok` payload, we need to turn that `ptr`
405+
// back into an integer. To avoid repeating logic we do that by
406+
// calling the transmute code, which is legal thanks to the size
407+
// assert we did when pulling it out of the pair.
408+
transmute_scalar(bx, imm, in_scalar, out_scalar)
389409
}
410+
BackendRepr::Scalar(_) | BackendRepr::SimdVector { .. } => imm,
390411
BackendRepr::ScalarPair(_, _) | BackendRepr::Memory { .. } => bug!(),
391412
})
392413
};
393-
394414
OperandRef { val, layout: field }
395415
}
396416

@@ -438,7 +458,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
438458
let tag_op = match self.val {
439459
OperandValue::ZeroSized => bug!(),
440460
OperandValue::Immediate(_) | OperandValue::Pair(_, _) => {
441-
self.extract_field(fx, bx, tag_field.as_usize())
461+
self.extract_field(fx, bx, None, tag_field)
442462
}
443463
OperandValue::Ref(place) => {
444464
let tag = place.with_type(self.layout).project_field(bx, tag_field.as_usize());
@@ -929,34 +949,28 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
929949
) -> Option<OperandRef<'tcx, Bx::Value>> {
930950
debug!("maybe_codegen_consume_direct(place_ref={:?})", place_ref);
931951

952+
let mut downcast_variant = None;
932953
match self.locals[place_ref.local] {
933954
LocalRef::Operand(mut o) => {
934955
// Moves out of scalar and scalar pair fields are trivial.
935956
for elem in place_ref.projection.iter() {
936-
match elem {
957+
match *elem {
937958
mir::ProjectionElem::Field(f, _) => {
938959
assert!(
939960
!o.layout.ty.is_any_ptr(),
940961
"Bad PlaceRef: destructing pointers should use cast/PtrMetadata, \
941962
but tried to access field {f:?} of pointer {o:?}",
942963
);
943-
o = o.extract_field(self, bx, f.index());
964+
o = o.extract_field(self, bx, downcast_variant, f);
965+
downcast_variant = None;
944966
}
945-
mir::ProjectionElem::Index(_)
946-
| mir::ProjectionElem::ConstantIndex { .. } => {
947-
// ZSTs don't require any actual memory access.
948-
// FIXME(eddyb) deduplicate this with the identical
949-
// checks in `codegen_consume` and `extract_field`.
950-
let elem = o.layout.field(bx.cx(), 0);
951-
if elem.is_zst() {
952-
o = OperandRef::zero_sized(elem);
953-
} else {
954-
return None;
955-
}
967+
mir::ProjectionElem::Downcast(_sym, variant_idx) => {
968+
downcast_variant = Some(variant_idx);
956969
}
957970
_ => return None,
958971
}
959972
}
973+
debug_assert_eq!(downcast_variant, None);
960974

961975
Some(o)
962976
}

tests/codegen/array-cmp.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool {
3939
// CHECK: %[[EQ00:.+]] = icmp eq i16 %[[A00]], %[[B00]]
4040
// CHECK-NEXT: br i1 %[[EQ00]], label %[[L01:.+]], label %[[EXIT_S:.+]]
4141

42+
// CHECK: [[EXIT_S]]:
43+
// CHECK: %[[RET_S:.+]] = icmp sle i16
44+
// CHECK: br label
45+
4246
// CHECK: [[L01]]:
4347
// CHECK: %[[PA01:.+]] = getelementptr{{.+}}i8, ptr %a, {{i32|i64}} 2
4448
// CHECK: %[[PB01:.+]] = getelementptr{{.+}}i8, ptr %b, {{i32|i64}} 2
@@ -66,8 +70,12 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool {
6670
// CHECK: %[[EQ11:.+]] = icmp eq i16 %[[A11]], %[[B11]]
6771
// CHECK-NEXT: br i1 %[[EQ11]], label %[[DONE:.+]], label %[[EXIT_U]]
6872

73+
// CHECK: [[EXIT_U]]:
74+
// CHECK: %[[RET_U:.+]] = icmp ule i16
75+
// CHECK: br label
76+
6977
// CHECK: [[DONE]]:
70-
// CHECK: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_S]] ], [ %{{.+}}, %[[EXIT_U]] ], [ true, %[[L11]] ]
78+
// CHECK: %[[RET:.+]] = phi i1 [ true, %[[L11]] ], [ %[[RET_S]], %[[EXIT_S]] ], [ %[[RET_U]], %[[EXIT_U]] ]
7179
// CHECK: ret i1 %[[RET]]
7280

7381
a <= b

tests/codegen/common_prim_int_ptr.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ pub unsafe fn extract_int(x: Result<usize, Box<()>>) -> usize {
4040
}
4141

4242
// CHECK-LABEL: @extract_box
43-
// CHECK-SAME: (i{{[0-9]+}} {{[^%]+}} [[DISCRIMINANT:%[0-9]+]], ptr {{[^%]+}} [[PAYLOAD:%[0-9]+]])
43+
// CHECK-SAME: (i{{[0-9]+}} {{[^%]+}} [[DISCRIMINANT:%x.0]], ptr {{[^%]+}} [[PAYLOAD:%x.1]])
4444
#[no_mangle]
4545
pub unsafe fn extract_box(x: Result<usize, Box<i32>>) -> Box<i32> {
46+
// CHECK: [[NOT_OK:%.+]] = icmp ne i{{[0-9]+}} [[DISCRIMINANT]], 0
47+
// CHECK: call void @llvm.assume(i1 [[NOT_OK]])
48+
// CHECK: [[NOT_NULL:%.+]] = icmp ne ptr [[PAYLOAD]], null
49+
// CHECK: call void @llvm.assume(i1 [[NOT_NULL]])
4650
// CHECK: ret ptr [[PAYLOAD]]
4751
match x {
4852
Ok(_) => std::intrinsics::unreachable(),

0 commit comments

Comments
 (0)