Skip to content

Commit 3b510de

Browse files
committed
Auto merge of #143502 - scottmcm:aggregate-simd, r=<try>
Let `rvalue_creates_operand` return true for *all* `Rvalue::Aggregate`s Draft for now because it's built on Ralf's #143291 Inspired by #138759 (comment) where I noticed that we were nearly at this point, plus the comments I was writing in #143410 that reminded me a type-dependent `true` is fine. This PR splits the `OperandRef::builder` logic out to a separate type, with the updates needed to handle SIMD as well. In doing so, that makes the existing `Aggregate` path in `codegen_rvalue_operand` capable of handing SIMD values just fine. As a result, we no longer need to do layout calculations for aggregate result types when running the analysis to determine which things can be SSA in codegen.
2 parents 6dec76f + 1d74a5a commit 3b510de

File tree

5 files changed

+229
-91
lines changed

5 files changed

+229
-91
lines changed

compiler/rustc_codegen_ssa/src/mir/analyze.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ impl<'a, 'b, 'tcx, Bx: BuilderMethods<'b, 'tcx>> Visitor<'tcx> for LocalAnalyzer
171171
if let Some(local) = place.as_local() {
172172
self.define(local, DefLocation::Assignment(location));
173173
if self.locals[local] != LocalKind::Memory {
174-
let decl_span = self.fx.mir.local_decls[local].source_info.span;
175-
if !self.fx.rvalue_creates_operand(rvalue, decl_span) {
174+
if !self.fx.rvalue_creates_operand(rvalue) {
176175
self.locals[local] = LocalKind::Memory;
177176
}
178177
}

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 109 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt;
22

3+
use itertools::Either;
34
use rustc_abi as abi;
45
use rustc_abi::{
56
Align, BackendRepr, FIRST_VARIANT, FieldIdx, Primitive, Size, TagEncoding, VariantIdx, Variants,
@@ -564,86 +565,127 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
564565
}
565566
}
566567
}
568+
}
567569

568-
/// Creates an incomplete operand containing the [`abi::Scalar`]s expected based
569-
/// on the `layout` passed. This is for use with [`OperandRef::insert_field`]
570-
/// later to set the necessary immediate(s).
571-
///
572-
/// Returns `None` for `layout`s which cannot be built this way.
573-
pub(crate) fn builder(
574-
layout: TyAndLayout<'tcx>,
575-
) -> Option<OperandRef<'tcx, Result<V, abi::Scalar>>> {
576-
// Uninhabited types are weird, because for example `Result<!, !>`
577-
// shows up as `FieldsShape::Primitive` and we need to be able to write
578-
// a field into `(u32, !)`. We'll do that in an `alloca` instead.
579-
if layout.uninhabited {
580-
return None;
581-
}
570+
/// Each of these variants starts out as `Either::Right` when it's uninitialized,
571+
/// then setting the field changes that to `Either::Left` with the backend value.
572+
#[derive(Debug, Copy, Clone)]
573+
enum OperandValueBuilder<V> {
574+
ZeroSized,
575+
Immediate(Either<V, abi::Scalar>),
576+
Pair(Either<V, abi::Scalar>, Either<V, abi::Scalar>),
577+
/// SIMD vectors need special handling because they're the only case where
578+
/// a type with a (non-ZST) `Memory`-ABI field can be `Scalar`-ABI.
579+
/// (Also you can't represent a vector type with just an `abi::Scalar`.)
580+
Vector(Either<V, ()>),
581+
}
582+
583+
/// Allows building up an `OperandRef` by setting fields one at a time.
584+
#[derive(Debug, Copy, Clone)]
585+
pub(super) struct OperandRefBuilder<'tcx, V> {
586+
val: OperandValueBuilder<V>,
587+
layout: TyAndLayout<'tcx>,
588+
}
582589

590+
impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
591+
/// Creates an uninitialized builder for an instance of the `layout`.
592+
///
593+
/// ICEs for [`BackendRepr::Memory`] types (other than ZSTs), which should
594+
/// be built up inside a [`PlaceRef`] instead as they need an allocated place
595+
/// into which to write the values of the fields.
596+
pub(super) fn new(layout: TyAndLayout<'tcx>) -> Self {
583597
let val = match layout.backend_repr {
584-
BackendRepr::Memory { .. } if layout.is_zst() => OperandValue::ZeroSized,
585-
BackendRepr::Scalar(s) => OperandValue::Immediate(Err(s)),
586-
BackendRepr::ScalarPair(a, b) => OperandValue::Pair(Err(a), Err(b)),
587-
BackendRepr::Memory { .. } | BackendRepr::SimdVector { .. } => return None,
598+
BackendRepr::Memory { .. } if layout.is_zst() => OperandValueBuilder::ZeroSized,
599+
BackendRepr::Scalar(s) => OperandValueBuilder::Immediate(Either::Right(s)),
600+
BackendRepr::ScalarPair(a, b) => {
601+
OperandValueBuilder::Pair(Either::Right(a), Either::Right(b))
602+
}
603+
BackendRepr::SimdVector { .. } => OperandValueBuilder::Vector(Either::Right(())),
604+
BackendRepr::Memory { .. } => {
605+
bug!("Cannot use non-ZST Memory-ABI type in operand builder: {layout:?}");
606+
}
588607
};
589-
Some(OperandRef { val, layout })
608+
OperandRefBuilder { val, layout }
590609
}
591-
}
592610

593-
impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, Result<V, abi::Scalar>> {
594-
pub(crate) fn insert_field<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
611+
pub(super) fn insert_field<Bx: BuilderMethods<'a, 'tcx, Value = V>>(
595612
&mut self,
596613
bx: &mut Bx,
597-
v: VariantIdx,
598-
f: FieldIdx,
614+
variant: VariantIdx,
615+
field: FieldIdx,
599616
operand: OperandRef<'tcx, V>,
600617
) {
601-
let (expect_zst, is_zero_offset) = if let abi::FieldsShape::Primitive = self.layout.fields {
618+
if let OperandValue::ZeroSized = operand.val {
619+
// A ZST never adds any state, so just ignore it.
620+
// This special-casing is worth it because of things like
621+
// `Result<!, !>` where `Ok(never)` is legal to write,
622+
// but the type shows as FieldShape::Primitive so we can't
623+
// actually look at the layout for the field being set.
624+
return;
625+
}
626+
627+
let is_zero_offset = if let abi::FieldsShape::Primitive = self.layout.fields {
602628
// The other branch looking at field layouts ICEs for primitives,
603629
// so we need to handle them separately.
604-
// Multiple fields is possible for cases such as aggregating
605-
// a thin pointer, where the second field is the unit.
630+
// Because we handled ZSTs above (like the metadata in a thin pointer),
631+
// the only possibility is that we're setting the one-and-only field.
606632
assert!(!self.layout.is_zst());
607-
assert_eq!(v, FIRST_VARIANT);
608-
let first_field = f == FieldIdx::ZERO;
609-
(!first_field, first_field)
633+
assert_eq!(variant, FIRST_VARIANT);
634+
assert_eq!(field, FieldIdx::ZERO);
635+
true
610636
} else {
611-
let variant_layout = self.layout.for_variant(bx.cx(), v);
612-
let field_layout = variant_layout.field(bx.cx(), f.as_usize());
613-
let field_offset = variant_layout.fields.offset(f.as_usize());
614-
(field_layout.is_zst(), field_offset == Size::ZERO)
637+
let variant_layout = self.layout.for_variant(bx.cx(), variant);
638+
let field_offset = variant_layout.fields.offset(field.as_usize());
639+
field_offset == Size::ZERO
615640
};
616641

617-
let mut update = |tgt: &mut Result<V, abi::Scalar>, src, from_scalar| {
618-
let to_scalar = tgt.unwrap_err();
642+
let mut update = |tgt: &mut Either<V, abi::Scalar>, src, from_scalar| {
643+
let to_scalar = tgt.unwrap_right();
644+
// We transmute here (rather than just `from_immediate`) because in
645+
// `Result<usize, *const ()>` the field of the `Ok` is an integer,
646+
// but the corresponding scalar in the enum is a pointer.
619647
let imm = transmute_scalar(bx, src, from_scalar, to_scalar);
620-
*tgt = Ok(imm);
648+
*tgt = Either::Left(imm);
621649
};
622650

623651
match (operand.val, operand.layout.backend_repr) {
624-
(OperandValue::ZeroSized, _) if expect_zst => {}
652+
(OperandValue::ZeroSized, _) => unreachable!("Handled above"),
625653
(OperandValue::Immediate(v), BackendRepr::Scalar(from_scalar)) => match &mut self.val {
626-
OperandValue::Immediate(val @ Err(_)) if is_zero_offset => {
654+
OperandValueBuilder::Immediate(val @ Either::Right(_)) if is_zero_offset => {
627655
update(val, v, from_scalar);
628656
}
629-
OperandValue::Pair(fst @ Err(_), _) if is_zero_offset => {
657+
OperandValueBuilder::Pair(fst @ Either::Right(_), _) if is_zero_offset => {
630658
update(fst, v, from_scalar);
631659
}
632-
OperandValue::Pair(_, snd @ Err(_)) if !is_zero_offset => {
660+
OperandValueBuilder::Pair(_, snd @ Either::Right(_)) if !is_zero_offset => {
633661
update(snd, v, from_scalar);
634662
}
635-
_ => bug!("Tried to insert {operand:?} into {v:?}.{f:?} of {self:?}"),
663+
_ => bug!("Tried to insert {operand:?} into {variant:?}.{field:?} of {self:?}"),
664+
},
665+
(OperandValue::Immediate(v), BackendRepr::SimdVector { .. }) => match &mut self.val {
666+
OperandValueBuilder::Vector(val @ Either::Right(())) if is_zero_offset => {
667+
*val = Either::Left(v);
668+
}
669+
_ => bug!("Tried to insert {operand:?} into {variant:?}.{field:?} of {self:?}"),
636670
},
637671
(OperandValue::Pair(a, b), BackendRepr::ScalarPair(from_sa, from_sb)) => {
638672
match &mut self.val {
639-
OperandValue::Pair(fst @ Err(_), snd @ Err(_)) => {
673+
OperandValueBuilder::Pair(fst @ Either::Right(_), snd @ Either::Right(_)) => {
640674
update(fst, a, from_sa);
641675
update(snd, b, from_sb);
642676
}
643-
_ => bug!("Tried to insert {operand:?} into {v:?}.{f:?} of {self:?}"),
677+
_ => bug!("Tried to insert {operand:?} into {variant:?}.{field:?} of {self:?}"),
644678
}
645679
}
646-
_ => bug!("Unsupported operand {operand:?} inserting into {v:?}.{f:?} of {self:?}"),
680+
(OperandValue::Ref(place), BackendRepr::Memory { .. }) => match &mut self.val {
681+
OperandValueBuilder::Vector(val @ Either::Right(())) => {
682+
let ibty = bx.cx().immediate_backend_type(self.layout);
683+
let simd = bx.load_from_place(ibty, place);
684+
*val = Either::Left(simd);
685+
}
686+
_ => bug!("Tried to insert {operand:?} into {variant:?}.{field:?} of {self:?}"),
687+
},
688+
_ => bug!("Operand cannot be used with `insert_field`: {operand:?}"),
647689
}
648690
}
649691

@@ -656,45 +698,50 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, Result<V, abi::Scalar>> {
656698
let field_offset = self.layout.fields.offset(f.as_usize());
657699
let is_zero_offset = field_offset == Size::ZERO;
658700
match &mut self.val {
659-
OperandValue::Immediate(val @ Err(_)) if is_zero_offset => {
660-
*val = Ok(imm);
701+
OperandValueBuilder::Immediate(val @ Either::Right(_)) if is_zero_offset => {
702+
*val = Either::Left(imm);
661703
}
662-
OperandValue::Pair(fst @ Err(_), _) if is_zero_offset => {
663-
*fst = Ok(imm);
704+
OperandValueBuilder::Pair(fst @ Either::Right(_), _) if is_zero_offset => {
705+
*fst = Either::Left(imm);
664706
}
665-
OperandValue::Pair(_, snd @ Err(_)) if !is_zero_offset => {
666-
*snd = Ok(imm);
707+
OperandValueBuilder::Pair(_, snd @ Either::Right(_)) if !is_zero_offset => {
708+
*snd = Either::Left(imm);
667709
}
668710
_ => bug!("Tried to insert {imm:?} into field {f:?} of {self:?}"),
669711
}
670712
}
671713

672714
/// After having set all necessary fields, this converts the
673-
/// `OperandValue<Result<V, _>>` (as obtained from [`OperandRef::builder`])
715+
/// `OperandValue<Either<V, _>>` (as obtained from [`OperandRef::builder`])
674716
/// to the normal `OperandValue<V>`.
675717
///
676718
/// ICEs if any required fields were not set.
677-
pub fn build(&self, cx: &impl CodegenMethods<'tcx, Value = V>) -> OperandRef<'tcx, V> {
678-
let OperandRef { val, layout } = *self;
719+
pub(super) fn build(&self, cx: &impl CodegenMethods<'tcx, Value = V>) -> OperandRef<'tcx, V> {
720+
let OperandRefBuilder { val, layout } = *self;
679721

680722
// For something like `Option::<u32>::None`, it's expected that the
681723
// payload scalar will not actually have been set, so this converts
682724
// unset scalars to corresponding `undef` values so long as the scalar
683725
// from the layout allows uninit.
684-
let unwrap = |r: Result<V, abi::Scalar>| match r {
685-
Ok(v) => v,
686-
Err(s) if s.is_uninit_valid() => {
726+
let unwrap = |r: Either<V, abi::Scalar>| match r {
727+
Either::Left(v) => v,
728+
Either::Right(s) if s.is_uninit_valid() => {
687729
let bty = cx.type_from_scalar(s);
688730
cx.const_undef(bty)
689731
}
690-
Err(_) => bug!("OperandRef::build called while fields are missing {self:?}"),
732+
Either::Right(_) => bug!("OperandRef::build called while fields are missing {self:?}"),
691733
};
692734

693735
let val = match val {
694-
OperandValue::ZeroSized => OperandValue::ZeroSized,
695-
OperandValue::Immediate(v) => OperandValue::Immediate(unwrap(v)),
696-
OperandValue::Pair(a, b) => OperandValue::Pair(unwrap(a), unwrap(b)),
697-
OperandValue::Ref(_) => bug!(),
736+
OperandValueBuilder::ZeroSized => OperandValue::ZeroSized,
737+
OperandValueBuilder::Immediate(v) => OperandValue::Immediate(unwrap(v)),
738+
OperandValueBuilder::Pair(a, b) => OperandValue::Pair(unwrap(a), unwrap(b)),
739+
OperandValueBuilder::Vector(v) => match v {
740+
Either::Left(v) => OperandValue::Immediate(v),
741+
Either::Right(()) => {
742+
bug!("OperandRef::build called while fields are missing {self:?}")
743+
}
744+
},
698745
};
699746
OperandRef { val, layout }
700747
}

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout};
44
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
55
use rustc_middle::{bug, mir};
66
use rustc_session::config::OptLevel;
7-
use rustc_span::{DUMMY_SP, Span};
87
use tracing::{debug, instrument};
98

10-
use super::operand::{OperandRef, OperandValue};
9+
use super::operand::{OperandRef, OperandRefBuilder, OperandValue};
1110
use super::place::{PlaceRef, codegen_tag_value};
1211
use super::{FunctionCx, LocalRef};
1312
use crate::common::{IntPredicate, TypeKind};
@@ -181,7 +180,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
181180
}
182181

183182
_ => {
184-
assert!(self.rvalue_creates_operand(rvalue, DUMMY_SP));
183+
assert!(self.rvalue_creates_operand(rvalue));
185184
let temp = self.codegen_rvalue_operand(bx, rvalue);
186185
temp.val.store(bx, dest);
187186
}
@@ -354,10 +353,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
354353
bx: &mut Bx,
355354
rvalue: &mir::Rvalue<'tcx>,
356355
) -> OperandRef<'tcx, Bx::Value> {
357-
assert!(
358-
self.rvalue_creates_operand(rvalue, DUMMY_SP),
359-
"cannot codegen {rvalue:?} to operand",
360-
);
356+
assert!(self.rvalue_creates_operand(rvalue), "cannot codegen {rvalue:?} to operand",);
361357

362358
match *rvalue {
363359
mir::Rvalue::Cast(ref kind, ref source, mir_cast_ty) => {
@@ -668,9 +664,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
668664

669665
// `rvalue_creates_operand` has arranged that we only get here if
670666
// we can build the aggregate immediate from the field immediates.
671-
let Some(mut builder) = OperandRef::builder(layout) else {
672-
bug!("Cannot use type in operand builder: {layout:?}")
673-
};
667+
let mut builder = OperandRefBuilder::new(layout);
674668
for (field_idx, field) in fields.iter_enumerated() {
675669
let op = self.codegen_operand(bx, field);
676670
let fi = active_field_index.unwrap_or(field_idx);
@@ -980,7 +974,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
980974
/// will not actually take the operand path because the result type is such
981975
/// that it always gets an `alloca`, but where it's not worth re-checking the
982976
/// layout in this code when the right thing will happen anyway.
983-
pub(crate) fn rvalue_creates_operand(&self, rvalue: &mir::Rvalue<'tcx>, span: Span) -> bool {
977+
pub(crate) fn rvalue_creates_operand(&self, rvalue: &mir::Rvalue<'tcx>) -> bool {
984978
match *rvalue {
985979
mir::Rvalue::Cast(mir::CastKind::Transmute, ref operand, cast_ty) => {
986980
let operand_ty = operand.ty(self.mir, self.cx.tcx());
@@ -1025,18 +1019,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10251019
mir::Rvalue::NullaryOp(..) |
10261020
mir::Rvalue::ThreadLocalRef(_) |
10271021
mir::Rvalue::Use(..) |
1022+
mir::Rvalue::Aggregate(..) | // (*)
10281023
mir::Rvalue::WrapUnsafeBinder(..) => // (*)
10291024
true,
10301025
// Arrays are always aggregates, so it's not worth checking anything here.
10311026
// (If it's really `[(); N]` or `[T; 0]` and we use the place path, fine.)
10321027
mir::Rvalue::Repeat(..) => false,
1033-
mir::Rvalue::Aggregate(..) => {
1034-
let ty = rvalue.ty(self.mir, self.cx.tcx());
1035-
let ty = self.monomorphize(ty);
1036-
let layout = self.cx.spanned_layout_of(ty, span);
1037-
OperandRef::<Bx::Value>::builder(layout).is_some()
1038-
}
1039-
}
1028+
}
10401029

10411030
// (*) this is only true if the type is suitable
10421031
}

tests/codegen/enum/enum-aggregate.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,14 @@ fn make_uninhabited_err_indirectly(n: Never) -> Result<u32, Never> {
112112

113113
#[no_mangle]
114114
fn make_fully_uninhabited_result(v: u32, n: Never) -> Result<(u32, Never), (Never, u32)> {
115-
// We don't try to do this in SSA form since the whole type is uninhabited.
115+
// Actually reaching this would be UB, so we don't actually build a result.
116116

117117
// CHECK-LABEL: { i32, i32 } @make_fully_uninhabited_result(i32 %v)
118-
// CHECK: %[[ALLOC_V:.+]] = alloca [4 x i8]
119-
// CHECK: %[[RET:.+]] = alloca [8 x i8]
120-
// CHECK: store i32 %v, ptr %[[ALLOC_V]]
121-
// CHECK: %[[TEMP_V:.+]] = load i32, ptr %[[ALLOC_V]]
122-
// CHECK: %[[INNER:.+]] = getelementptr inbounds i8, ptr %[[RET]]
123-
// CHECK: store i32 %[[TEMP_V]], ptr %[[INNER]]
124-
// CHECK: call void @llvm.trap()
125-
// CHECK: unreachable
118+
// CHECK-NEXT: start:
119+
// CHECK-NEXT: call void @llvm.trap()
120+
// CHECK-NEXT: call void @llvm.trap()
121+
// CHECK-NEXT: call void @llvm.trap()
122+
// CHECK-NEXT: unreachable
126123
Ok((v, n))
127124
}
128125

0 commit comments

Comments
 (0)