Skip to content

Commit 22dd82f

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 2f9c9ce + 4815754 commit 22dd82f

File tree

12 files changed

+385
-122
lines changed

12 files changed

+385
-122
lines changed

compiler/rustc_codegen_ssa/src/mir/analyze.rs

Lines changed: 99 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
//! An analysis to determine which locals require allocas and
22
//! which do not.
33
4+
use rustc_abi as abi;
45
use rustc_data_structures::graph::dominators::Dominators;
56
use rustc_index::bit_set::DenseBitSet;
67
use rustc_index::{IndexSlice, IndexVec};
78
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
89
use rustc_middle::mir::{self, DefLocation, Location, TerminatorKind, traversal};
9-
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
10+
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
1011
use rustc_middle::{bug, span_bug};
1112
use tracing::debug;
1213

@@ -99,63 +100,113 @@ impl<'a, 'b, 'tcx, Bx: BuilderMethods<'b, 'tcx>> LocalAnalyzer<'a, 'b, 'tcx, Bx>
99100
context: PlaceContext,
100101
location: Location,
101102
) {
102-
let cx = self.fx.cx;
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);
111+
}
112+
}
103113

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-
};
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;
118+
}
110119

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;
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;
127+
}
128+
129+
if !place_ref.projection.is_empty() {
130+
if context.is_mutating_use() {
131+
// If it's a mutating use it doesn't matter what the projections are,
132+
// if there are *any* then we need a place to write. (For example,
133+
// `_1 = Foo()` works in SSA but `_2.0 = Foo()` does not.)
134+
let mut_projection = PlaceContext::MutatingUse(MutatingUseContext::Projection);
135+
self.visit_local(place_ref.local, mut_projection, location);
136+
return;
137+
}
138+
139+
// Scan through to ensure the only projections are those which
140+
// `FunctionCx::maybe_codegen_consume_direct` can handle.
141+
let base_ty = self.fx.monomorphized_place_ty(mir::PlaceRef::from(place_ref.local));
142+
let mut layout = self.fx.cx.layout_of(base_ty);
143+
for elem in place_ref.projection {
144+
if layout.is_zst() {
145+
// Any further projections must still be ZSTs, so we're good.
146+
break;
127147
}
128148

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;
149+
#[track_caller]
150+
fn compatible_projection(src: TyAndLayout<'_>, tgt: TyAndLayout<'_>) -> bool {
151+
fn compatible_initness(a: abi::Scalar, b: abi::Scalar) -> bool {
152+
!a.is_uninit_valid() || b.is_uninit_valid()
153+
}
154+
155+
use abi::BackendRepr::*;
156+
match (src.backend_repr, tgt.backend_repr) {
157+
_ if tgt.is_zst() => true,
158+
(Scalar(a), Scalar(b))
159+
| (SimdVector { element: a, .. }, SimdVector { element: b, .. }) => {
160+
compatible_initness(a, b)
161+
}
162+
(ScalarPair(a0, a1), Scalar(b)) => {
163+
compatible_initness(a0, b) && compatible_initness(a1, b)
164+
}
165+
(ScalarPair(a0, a1), ScalarPair(b0, b1)) => {
166+
compatible_initness(a0, b0) && compatible_initness(a1, b1)
167+
}
168+
// This arm is a hack; remove it as part of
169+
// <https://github.com/rust-lang/compiler-team/issues/838>
170+
(SimdVector { .. }, Memory { .. }) => true,
171+
_ => bug!("Mismatched layouts in analysis\nsrc: {src:?}\ntgt: {tgt:?}"),
136172
}
137173
}
138-
}
139174

140-
if let mir::ProjectionElem::Deref = elem {
141-
// Deref projections typically only read the pointer.
142-
base_context = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
143-
}
175+
match *elem {
176+
mir::PlaceElem::Field(fidx, ..) => {
177+
let field_layout = layout.field(self.fx.cx, fidx.as_usize());
178+
if compatible_projection(layout, field_layout) {
179+
layout = field_layout;
180+
continue;
181+
}
182+
}
183+
mir::PlaceElem::Downcast(_, vidx) => {
184+
let variant_layout = layout.for_variant(self.fx.cx, vidx);
185+
if compatible_projection(layout, variant_layout) {
186+
layout = variant_layout;
187+
continue;
188+
}
189+
}
190+
191+
mir::PlaceElem::Index(..)
192+
| mir::PlaceElem::ConstantIndex { .. }
193+
| mir::PlaceElem::Subslice { .. }
194+
| mir::PlaceElem::OpaqueCast(..)
195+
| mir::PlaceElem::UnwrapUnsafeBinder(..)
196+
| mir::PlaceElem::Subtype(..) => {}
197+
198+
mir::PlaceElem::Deref => bug!("Deref after first position"),
199+
}
144200

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-
);
201+
// If the above didn't `continue`, we can't handle the projection.
202+
self.locals[place_ref.local] = LocalKind::Memory;
203+
return;
155204
}
156-
} else {
157-
self.visit_local(place_ref.local, context, location);
158205
}
206+
207+
// Even with supported projections, we still need to have `visit_local`
208+
// check for things that can't be done in SSA (like `SharedBorrow`).
209+
self.visit_local(place_ref.local, context, location);
159210
}
160211
}
161212

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: 30 additions & 33 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
}
@@ -361,13 +361,17 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
361361
let (in_scalar, imm) = match (self.val, self.layout.backend_repr) {
362362
// Extract a scalar component from a pair.
363363
(OperandValue::Pair(a_llval, b_llval), BackendRepr::ScalarPair(a, b)) => {
364-
if offset.bytes() == 0 {
364+
// This needs to look at `offset`, rather than `i`, because
365+
// for a type like `Option<u32>`, the first thing in the pair
366+
// is the tag, so `(_2 as Some).0` needs to read the *second*
367+
// thing in the pair despite it being "field zero".
368+
if offset == Size::ZERO {
365369
assert_eq!(field.size, a.size(bx.cx()));
366-
(Some(a), a_llval)
370+
(a, a_llval)
367371
} else {
368372
assert_eq!(offset, a.size(bx.cx()).align_to(b.align(bx.cx()).abi));
369373
assert_eq!(field.size, b.size(bx.cx()));
370-
(Some(b), b_llval)
374+
(b, b_llval)
371375
}
372376
}
373377

@@ -378,23 +382,12 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
378382
OperandValue::Immediate(match field.backend_repr {
379383
BackendRepr::SimdVector { .. } => imm,
380384
BackendRepr::Scalar(out_scalar) => {
381-
let Some(in_scalar) = in_scalar else {
382-
span_bug!(
383-
fx.mir.span,
384-
"OperandRef::extract_field({:?}): missing input scalar for output scalar",
385-
self
386-
)
387-
};
388-
if in_scalar != out_scalar {
389-
// If the backend and backend_immediate types might differ,
390-
// flip back to the backend type then to the new immediate.
391-
// This avoids nop truncations, but still handles things like
392-
// Bools in union fields needs to be truncated.
393-
let backend = bx.from_immediate(imm);
394-
bx.to_immediate_scalar(backend, out_scalar)
395-
} else {
396-
imm
397-
}
385+
// For a type like `Result<usize, &u32>` the layout is `Pair(i64, ptr)`.
386+
// But if we're reading the `Ok` payload, we need to turn that `ptr`
387+
// back into an integer. To avoid repeating logic we do that by
388+
// calling the transmute code, which is legal thanks to the size
389+
// assert we did when pulling it out of the pair.
390+
transmute_scalar(bx, imm, in_scalar, out_scalar)
398391
}
399392
BackendRepr::ScalarPair(_, _) | BackendRepr::Memory { .. } => bug!(),
400393
})
@@ -875,7 +868,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
875868
LocalRef::Operand(mut o) => {
876869
// Moves out of scalar and scalar pair fields are trivial.
877870
for elem in place_ref.projection.iter() {
878-
match elem {
871+
match *elem {
879872
mir::ProjectionElem::Field(f, _) => {
880873
assert!(
881874
!o.layout.ty.is_any_ptr(),
@@ -884,17 +877,21 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
884877
);
885878
o = o.extract_field(self, bx, f.index());
886879
}
887-
mir::ProjectionElem::Index(_)
888-
| mir::ProjectionElem::ConstantIndex { .. } => {
889-
// ZSTs don't require any actual memory access.
890-
// FIXME(eddyb) deduplicate this with the identical
891-
// checks in `codegen_consume` and `extract_field`.
892-
let elem = o.layout.field(bx.cx(), 0);
893-
if elem.is_zst() {
894-
o = OperandRef::zero_sized(elem);
895-
} else {
896-
return None;
897-
}
880+
mir::ProjectionElem::Downcast(_sym, variant_idx) => {
881+
let layout = o.layout.for_variant(bx.cx(), variant_idx);
882+
let val = match layout.backend_repr {
883+
// The transmute here handles cases like `Result<bool, u8>`
884+
// where the immediate values need to change for
885+
// the specific types in the cast-to variant.
886+
BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..) => {
887+
self.codegen_transmute_operand(bx, o, layout)
888+
}
889+
BackendRepr::SimdVector { .. } | BackendRepr::Memory { .. } => {
890+
o.val
891+
}
892+
};
893+
894+
o = OperandRef { val, layout };
898895
}
899896
_ => return None,
900897
}

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)