Skip to content

Commit cf39981

Browse files
committed
Auto merge of #138759 - scottmcm:operand-builder, r=<try>
Allow `enum` and `union` literals to also create SSA values Today, `Some(x)` always goes through an `alloca`, even in trivial cases where the niching means the constructor doesn't even change the value. For example, <https://rust.godbolt.org/z/6KG6PqoYz> ```rust pub fn demo(r: &i32) -> Option<&i32> { Some(r) } ``` currently emits the IR ```llvm define align 4 ptr `@demo(ptr` align 4 %r) unnamed_addr { start: %_0 = alloca [8 x i8], align 8 store ptr %r, ptr %_0, align 8 %0 = load ptr, ptr %_0, align 8 ret ptr %0 } ``` but with this PR it becomes just ```llvm define align 4 ptr `@demo(ptr` align 4 %r) unnamed_addr { start: ret ptr %r } ``` (Of course the optimizer can clean that up, but it'd be nice if it didn't have to -- especially in debug where it doesn't run. This is like #123886, but that only handled non-simd `struct`s -- this PR generalizes it to all non-simd ADTs.) Doing this means handing variants other than `FIRST_VARIANT`, handling the active field for unions, refactoring the discriminant code so the Place and Operand parts can share the calculation, etc. Other PRs that led up to this one: - #142005 - #142103 - #142324 - #142383
2 parents 8a65ee0 + 3c75d48 commit cf39981

File tree

8 files changed

+376
-79
lines changed

8 files changed

+376
-79
lines changed

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,13 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
580580
pub(crate) fn builder(
581581
layout: TyAndLayout<'tcx>,
582582
) -> Option<OperandRef<'tcx, Result<V, abi::Scalar>>> {
583+
// Uninhabited types are weird, because for example `Result<!, !>`
584+
// shows up as `FieldsShape::Primitive` and we need to be able to write
585+
// a field into `(u32, !)`. We'll do that in an `alloca` instead.
586+
if layout.uninhabited {
587+
return None;
588+
}
589+
583590
let val = match layout.backend_repr {
584591
BackendRepr::Memory { .. } if layout.is_zst() => OperandValue::ZeroSized,
585592
BackendRepr::Scalar(s) => OperandValue::Immediate(Err(s)),
@@ -649,16 +656,46 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, Result<V, abi::Scalar>> {
649656
}
650657
}
651658

659+
/// Insert the immediate value `imm` for field `f` in the *type itself*,
660+
/// rather than into one of the variants.
661+
///
662+
/// Most things want [`OperandRef::insert_field`] instead, but this one is
663+
/// necessary for writing things like enum tags that aren't in any variant.
664+
pub(super) fn insert_imm(&mut self, f: FieldIdx, imm: V) {
665+
let field_offset = self.layout.fields.offset(f.as_usize());
666+
let is_zero_offset = field_offset == Size::ZERO;
667+
match &mut self.val {
668+
OperandValue::Immediate(val @ Err(_)) if is_zero_offset => {
669+
*val = Ok(imm);
670+
}
671+
OperandValue::Pair(fst @ Err(_), _) if is_zero_offset => {
672+
*fst = Ok(imm);
673+
}
674+
OperandValue::Pair(_, snd @ Err(_)) if !is_zero_offset => {
675+
*snd = Ok(imm);
676+
}
677+
_ => bug!("Tried to insert {imm:?} into field {f:?} of {self:?}"),
678+
}
679+
}
680+
652681
/// After having set all necessary fields, this converts the
653682
/// `OperandValue<Result<V, _>>` (as obtained from [`OperandRef::builder`])
654683
/// to the normal `OperandValue<V>`.
655684
///
656685
/// ICEs if any required fields were not set.
657-
pub fn build(&self) -> OperandRef<'tcx, V> {
686+
pub fn build(&self, cx: &impl CodegenMethods<'tcx, Value = V>) -> OperandRef<'tcx, V> {
658687
let OperandRef { val, layout } = *self;
659688

689+
// For something like `Option::<u32>::None`, it's expected that the
690+
// payload scalar will not actually have been set, so this converts
691+
// unset scalars to corresponding `undef` values so long as the scalar
692+
// from the layout allows uninit.
660693
let unwrap = |r: Result<V, abi::Scalar>| match r {
661694
Ok(v) => v,
695+
Err(s) if s.is_uninit_valid() => {
696+
let bty = cx.type_from_scalar(s);
697+
cx.const_undef(bty)
698+
}
662699
Err(_) => bug!("OperandRef::build called while fields are missing {self:?}"),
663700
};
664701

compiler/rustc_codegen_ssa/src/mir/place.rs

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use rustc_abi::{Align, BackendRepr, FieldsShape, Size, TagEncoding, VariantIdx, Variants};
1+
use rustc_abi::{
2+
Align, BackendRepr, FieldIdx, FieldsShape, Size, TagEncoding, VariantIdx, Variants,
3+
};
24
use rustc_middle::mir::PlaceTy;
35
use rustc_middle::mir::interpret::Scalar;
46
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout};
@@ -239,53 +241,17 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
239241
bx: &mut Bx,
240242
variant_index: VariantIdx,
241243
) {
242-
if self.layout.for_variant(bx.cx(), variant_index).is_uninhabited() {
243-
// We play it safe by using a well-defined `abort`, but we could go for immediate UB
244-
// if that turns out to be helpful.
245-
bx.abort();
246-
return;
247-
}
248-
match self.layout.variants {
249-
Variants::Empty => unreachable!("we already handled uninhabited types"),
250-
Variants::Single { index } => assert_eq!(index, variant_index),
251-
252-
Variants::Multiple { tag_encoding: TagEncoding::Direct, tag_field, .. } => {
253-
let ptr = self.project_field(bx, tag_field.as_usize());
254-
let to =
255-
self.layout.ty.discriminant_for_variant(bx.tcx(), variant_index).unwrap().val;
256-
bx.store_to_place(
257-
bx.cx().const_uint_big(bx.cx().backend_type(ptr.layout), to),
258-
ptr.val,
259-
);
244+
match codegen_tag_value(bx.cx(), variant_index, self.layout) {
245+
Err(UninhabitedVariantError) => {
246+
// We play it safe by using a well-defined `abort`, but we could go for immediate UB
247+
// if that turns out to be helpful.
248+
bx.abort();
260249
}
261-
Variants::Multiple {
262-
tag_encoding:
263-
TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start },
264-
tag_field,
265-
..
266-
} => {
267-
if variant_index != untagged_variant {
268-
let niche = self.project_field(bx, tag_field.as_usize());
269-
let niche_llty = bx.cx().immediate_backend_type(niche.layout);
270-
let BackendRepr::Scalar(scalar) = niche.layout.backend_repr else {
271-
bug!("expected a scalar placeref for the niche");
272-
};
273-
// We are supposed to compute `niche_value.wrapping_add(niche_start)` wrapping
274-
// around the `niche`'s type.
275-
// The easiest way to do that is to do wrapping arithmetic on `u128` and then
276-
// masking off any extra bits that occur because we did the arithmetic with too many bits.
277-
let niche_value = variant_index.as_u32() - niche_variants.start().as_u32();
278-
let niche_value = (niche_value as u128).wrapping_add(niche_start);
279-
let niche_value = niche_value & niche.layout.size.unsigned_int_max();
280-
281-
let niche_llval = bx.cx().scalar_to_backend(
282-
Scalar::from_uint(niche_value, niche.layout.size),
283-
scalar,
284-
niche_llty,
285-
);
286-
OperandValue::Immediate(niche_llval).store(bx, niche);
287-
}
250+
Ok(Some((tag_field, imm))) => {
251+
let tag_place = self.project_field(bx, tag_field.as_usize());
252+
OperandValue::Immediate(imm).store(bx, tag_place);
288253
}
254+
Ok(None) => {}
289255
}
290256
}
291257

@@ -471,3 +437,73 @@ fn round_up_const_value_to_alignment<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
471437
let offset = bx.and(neg_value, align_minus_1);
472438
bx.add(value, offset)
473439
}
440+
441+
/// Calculates the value that needs to be stored to mark the discriminant.
442+
///
443+
/// This might be `None` for a `struct` or a niched variant (like `Some(&3)`).
444+
///
445+
/// If it's `Some`, it returns the value to store and the field in which to
446+
/// store it. Note that this value is *not* the same as the discriminant, in
447+
/// general, as it might be a niche value or have a different size.
448+
///
449+
/// It might also be an `Err` because the variant is uninhabited.
450+
pub(super) fn codegen_tag_value<'tcx, V>(
451+
cx: &impl CodegenMethods<'tcx, Value = V>,
452+
variant_index: VariantIdx,
453+
layout: TyAndLayout<'tcx>,
454+
) -> Result<Option<(FieldIdx, V)>, UninhabitedVariantError> {
455+
// By checking uninhabited-ness first we don't need to worry about types
456+
// like `(u32, !)` which are single-variant but weird.
457+
if layout.for_variant(cx, variant_index).is_uninhabited() {
458+
return Err(UninhabitedVariantError);
459+
}
460+
461+
Ok(match layout.variants {
462+
Variants::Empty => unreachable!("we already handled uninhabited types"),
463+
Variants::Single { index } => {
464+
assert_eq!(index, variant_index);
465+
None
466+
}
467+
468+
Variants::Multiple { tag_encoding: TagEncoding::Direct, tag_field, .. } => {
469+
let discr = layout.ty.discriminant_for_variant(cx.tcx(), variant_index);
470+
let to = discr.unwrap().val;
471+
let tag_layout = layout.field(cx, tag_field.as_usize());
472+
let tag_llty = cx.immediate_backend_type(tag_layout);
473+
let imm = cx.const_uint_big(tag_llty, to);
474+
Some((tag_field, imm))
475+
}
476+
Variants::Multiple {
477+
tag_encoding: TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start },
478+
tag_field,
479+
..
480+
} => {
481+
if variant_index != untagged_variant {
482+
let niche_layout = layout.field(cx, tag_field.as_usize());
483+
let niche_llty = cx.immediate_backend_type(niche_layout);
484+
let BackendRepr::Scalar(scalar) = niche_layout.backend_repr else {
485+
bug!("expected a scalar placeref for the niche");
486+
};
487+
// We are supposed to compute `niche_value.wrapping_add(niche_start)` wrapping
488+
// around the `niche`'s type.
489+
// The easiest way to do that is to do wrapping arithmetic on `u128` and then
490+
// masking off any extra bits that occur because we did the arithmetic with too many bits.
491+
let niche_value = variant_index.as_u32() - niche_variants.start().as_u32();
492+
let niche_value = (niche_value as u128).wrapping_add(niche_start);
493+
let niche_value = niche_value & niche_layout.size.unsigned_int_max();
494+
495+
let niche_llval = cx.scalar_to_backend(
496+
Scalar::from_uint(niche_value, niche_layout.size),
497+
scalar,
498+
niche_llty,
499+
);
500+
Some((tag_field, niche_llval))
501+
} else {
502+
None
503+
}
504+
}
505+
})
506+
}
507+
508+
#[derive(Debug)]
509+
pub(super) struct UninhabitedVariantError;

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_span::{DUMMY_SP, Span};
1010
use tracing::{debug, instrument};
1111

1212
use super::operand::{OperandRef, OperandValue};
13-
use super::place::PlaceRef;
13+
use super::place::{PlaceRef, codegen_tag_value};
1414
use super::{FunctionCx, LocalRef};
1515
use crate::common::IntPredicate;
1616
use crate::traits::*;
@@ -700,7 +700,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
700700
}
701701
mir::Rvalue::Use(ref operand) => self.codegen_operand(bx, operand),
702702
mir::Rvalue::Repeat(..) => bug!("{rvalue:?} in codegen_rvalue_operand"),
703-
mir::Rvalue::Aggregate(_, ref fields) => {
703+
mir::Rvalue::Aggregate(ref kind, ref fields) => {
704+
let (variant_index, active_field_index) = match **kind {
705+
mir::AggregateKind::Adt(_, variant_index, _, _, active_field_index) => {
706+
(variant_index, active_field_index)
707+
}
708+
_ => (FIRST_VARIANT, None),
709+
};
710+
704711
let ty = rvalue.ty(self.mir, self.cx.tcx());
705712
let ty = self.monomorphize(ty);
706713
let layout = self.cx.layout_of(ty);
@@ -712,10 +719,27 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
712719
};
713720
for (field_idx, field) in fields.iter_enumerated() {
714721
let op = self.codegen_operand(bx, field);
715-
builder.insert_field(bx, FIRST_VARIANT, field_idx, op);
722+
let fi = active_field_index.unwrap_or(field_idx);
723+
builder.insert_field(bx, variant_index, fi, op);
716724
}
717725

718-
builder.build()
726+
let tag_result = codegen_tag_value(self.cx, variant_index, layout);
727+
match tag_result {
728+
Err(super::place::UninhabitedVariantError) => {
729+
// Like codegen_set_discr we use a sound abort, but could
730+
// potentially `unreachable` or just return the poison for
731+
// more optimizability, if that turns out to be helpful.
732+
bx.abort();
733+
let val = OperandValue::poison(bx, layout);
734+
OperandRef { val, layout }
735+
}
736+
Ok(maybe_tag_value) => {
737+
if let Some((tag_field, tag_imm)) = maybe_tag_value {
738+
builder.insert_imm(tag_field, tag_imm);
739+
}
740+
builder.build(bx.cx())
741+
}
742+
}
719743
}
720744
mir::Rvalue::ShallowInitBox(ref operand, content_ty) => {
721745
let operand = self.codegen_operand(bx, operand);
@@ -1043,26 +1067,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10431067
// Arrays are always aggregates, so it's not worth checking anything here.
10441068
// (If it's really `[(); N]` or `[T; 0]` and we use the place path, fine.)
10451069
mir::Rvalue::Repeat(..) => false,
1046-
mir::Rvalue::Aggregate(ref kind, _) => {
1047-
let allowed_kind = match **kind {
1048-
// This always produces a `ty::RawPtr`, so will be Immediate or Pair
1049-
mir::AggregateKind::RawPtr(..) => true,
1050-
mir::AggregateKind::Array(..) => false,
1051-
mir::AggregateKind::Tuple => true,
1052-
mir::AggregateKind::Adt(def_id, ..) => {
1053-
let adt_def = self.cx.tcx().adt_def(def_id);
1054-
adt_def.is_struct() && !adt_def.repr().simd()
1055-
}
1056-
mir::AggregateKind::Closure(..) => true,
1057-
// FIXME: Can we do this for simple coroutines too?
1058-
mir::AggregateKind::Coroutine(..) | mir::AggregateKind::CoroutineClosure(..) => false,
1059-
};
1060-
allowed_kind && {
1061-
let ty = rvalue.ty(self.mir, self.cx.tcx());
1062-
let ty = self.monomorphize(ty);
1063-
let layout = self.cx.spanned_layout_of(ty, span);
1064-
OperandRef::<Bx::Value>::builder(layout).is_some()
1065-
}
1070+
mir::Rvalue::Aggregate(..) => {
1071+
let ty = rvalue.ty(self.mir, self.cx.tcx());
1072+
let ty = self.monomorphize(ty);
1073+
let layout = self.cx.spanned_layout_of(ty, span);
1074+
OperandRef::<Bx::Value>::builder(layout).is_some()
10661075
}
10671076
}
10681077

tests/codegen/align-struct.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ pub struct Nested64 {
1515
d: i8,
1616
}
1717

18+
// This has the extra field in B to ensure it's not ScalarPair,
19+
// and thus that the test actually emits it via memory, not `insertvalue`.
1820
pub enum Enum4 {
1921
A(i32),
20-
B(i32),
22+
B(i32, i32),
2123
}
2224

2325
pub enum Enum64 {
@@ -54,7 +56,7 @@ pub fn nested64(a: Align64, b: i32, c: i32, d: i8) -> Nested64 {
5456
// CHECK-LABEL: @enum4
5557
#[no_mangle]
5658
pub fn enum4(a: i32) -> Enum4 {
57-
// CHECK: %e4 = alloca [8 x i8], align 4
59+
// CHECK: %e4 = alloca [12 x i8], align 4
5860
let e4 = Enum4::A(a);
5961
e4
6062
}

tests/codegen/common_prim_int_ptr.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
#[no_mangle]
1212
pub fn insert_int(x: usize) -> Result<usize, Box<()>> {
1313
// CHECK: start:
14-
// CHECK-NEXT: inttoptr i{{[0-9]+}} %x to ptr
15-
// CHECK-NEXT: insertvalue
16-
// CHECK-NEXT: ret { i{{[0-9]+}}, ptr }
14+
// CHECK-NEXT: %[[WO_PROV:.+]] = getelementptr i8, ptr null, [[USIZE:i[0-9]+]] %x
15+
// CHECK-NEXT: %[[R:.+]] = insertvalue { [[USIZE]], ptr } { [[USIZE]] 0, ptr poison }, ptr %[[WO_PROV]], 1
16+
// CHECK-NEXT: ret { [[USIZE]], ptr } %[[R]]
1717
Ok(x)
1818
}
1919

0 commit comments

Comments
 (0)