Skip to content

Commit 6e1d947

Browse files
committed
Auto merge of #123886 - scottmcm:more-rvalue-operands, r=matthewjasper
Avoid `alloca`s in codegen for simple `mir::Aggregate` statements The core idea here is to remove the abstraction penalty of simple newtypes in codegen. Even something simple like constructing a ```rust #[repr(transparent)] struct Foo(u32); ``` forces an `alloca` to be generated in nightly right now. Certainly LLVM can optimize that away, but it would be nice if it didn't have to. Quick example: ```rust #[repr(transparent)] pub struct Transparent32(u32); #[no_mangle] pub fn make_transparent(x: u32) -> Transparent32 { let a = Transparent32(x); a } ``` on nightly we produce <https://rust.godbolt.org/z/zcvoM79ae> ```llvm define noundef i32 `@make_transparent(i32` noundef %x) unnamed_addr #0 { %a = alloca i32, align 4 store i32 %x, ptr %a, align 4 %0 = load i32, ptr %a, align 4, !noundef !3 ret i32 %0 } ``` but after this PR we produce ```llvm define noundef i32 `@make_transparent(i32` noundef %x) unnamed_addr #0 { start: ret i32 %x } ``` (even before the optimizer runs).
2 parents 2cce088 + c38f75c commit 6e1d947

File tree

5 files changed

+231
-14
lines changed

5 files changed

+231
-14
lines changed

Cargo.lock

+2
Original file line numberDiff line numberDiff line change
@@ -3746,8 +3746,10 @@ name = "rustc_codegen_ssa"
37463746
version = "0.0.0"
37473747
dependencies = [
37483748
"ar_archive_writer",
3749+
"arrayvec",
37493750
"bitflags 2.5.0",
37503751
"cc",
3752+
"either",
37513753
"itertools 0.12.1",
37523754
"jobserver",
37533755
"libc",

compiler/rustc_codegen_ssa/Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ edition = "2021"
66
[dependencies]
77
# tidy-alphabetical-start
88
ar_archive_writer = "0.2.0"
9+
arrayvec = { version = "0.7", default-features = false }
910
bitflags = "2.4.1"
10-
cc = "1.0.97"
11+
cc = "1.0.90"
12+
either = "1.5.0"
1113
itertools = "0.12"
1214
jobserver = "0.1.28"
1315
pathdiff = "0.2.0"

compiler/rustc_codegen_ssa/src/mir/operand.rs

+30
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use rustc_target::abi::{self, Abi, Align, Size};
1414

1515
use std::fmt;
1616

17+
use arrayvec::ArrayVec;
18+
use either::Either;
19+
1720
/// The representation of a Rust value. The enum variant is in fact
1821
/// uniquely determined by the value's type, but is kept as a
1922
/// safety check.
@@ -58,6 +61,33 @@ pub enum OperandValue<V> {
5861
ZeroSized,
5962
}
6063

64+
impl<V> OperandValue<V> {
65+
/// If this is ZeroSized/Immediate/Pair, return an array of the 0/1/2 values.
66+
/// If this is Ref, return the place.
67+
#[inline]
68+
pub fn immediates_or_place(self) -> Either<ArrayVec<V, 2>, PlaceValue<V>> {
69+
match self {
70+
OperandValue::ZeroSized => Either::Left(ArrayVec::new()),
71+
OperandValue::Immediate(a) => Either::Left(ArrayVec::from_iter([a])),
72+
OperandValue::Pair(a, b) => Either::Left([a, b].into()),
73+
OperandValue::Ref(p) => Either::Right(p),
74+
}
75+
}
76+
77+
/// Given an array of 0/1/2 immediate values, return ZeroSized/Immediate/Pair.
78+
#[inline]
79+
pub fn from_immediates(immediates: ArrayVec<V, 2>) -> Self {
80+
let mut it = immediates.into_iter();
81+
let Some(a) = it.next() else {
82+
return OperandValue::ZeroSized;
83+
};
84+
let Some(b) = it.next() else {
85+
return OperandValue::Immediate(a);
86+
};
87+
OperandValue::Pair(a, b)
88+
}
89+
}
90+
6191
/// An `OperandRef` is an "SSA" reference to a Rust value, along with
6292
/// its type.
6393
///

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+73-13
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ use crate::traits::*;
88
use crate::MemFlags;
99

1010
use rustc_hir as hir;
11-
use rustc_middle::mir::{self, AggregateKind, Operand};
11+
use rustc_middle::mir;
1212
use rustc_middle::ty::cast::{CastTy, IntTy};
1313
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout};
1414
use rustc_middle::ty::{self, adjustment::PointerCoercion, Instance, Ty, TyCtxt};
1515
use rustc_middle::{bug, span_bug};
1616
use rustc_session::config::OptLevel;
1717
use rustc_span::{Span, DUMMY_SP};
18-
use rustc_target::abi::{self, FIRST_VARIANT};
18+
use rustc_target::abi::{self, FieldIdx, FIRST_VARIANT};
19+
20+
use arrayvec::ArrayVec;
1921

2022
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
2123
#[instrument(level = "trace", skip(self, bx))]
@@ -579,7 +581,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
579581
self.codegen_place_to_pointer(bx, place, mk_ref)
580582
}
581583

582-
mir::Rvalue::CopyForDeref(place) => self.codegen_operand(bx, &Operand::Copy(place)),
584+
mir::Rvalue::CopyForDeref(place) => {
585+
self.codegen_operand(bx, &mir::Operand::Copy(place))
586+
}
583587
mir::Rvalue::AddressOf(mutability, place) => {
584588
let mk_ptr =
585589
move |tcx: TyCtxt<'tcx>, ty: Ty<'tcx>| Ty::new_ptr(tcx, ty, mutability);
@@ -736,11 +740,41 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
736740
_ => bug!("RawPtr operands {data:?} {meta:?}"),
737741
}
738742
}
739-
mir::Rvalue::Repeat(..) | mir::Rvalue::Aggregate(..) => {
740-
// According to `rvalue_creates_operand`, only ZST
741-
// aggregate rvalues are allowed to be operands.
743+
mir::Rvalue::Repeat(..) => bug!("{rvalue:?} in codegen_rvalue_operand"),
744+
mir::Rvalue::Aggregate(_, ref fields) => {
742745
let ty = rvalue.ty(self.mir, self.cx.tcx());
743-
OperandRef::zero_sized(self.cx.layout_of(self.monomorphize(ty)))
746+
let ty = self.monomorphize(ty);
747+
let layout = self.cx.layout_of(ty);
748+
749+
// `rvalue_creates_operand` has arranged that we only get here if
750+
// we can build the aggregate immediate from the field immediates.
751+
let mut inputs = ArrayVec::<Bx::Value, 2>::new();
752+
let mut input_scalars = ArrayVec::<abi::Scalar, 2>::new();
753+
for field_idx in layout.fields.index_by_increasing_offset() {
754+
let field_idx = FieldIdx::from_usize(field_idx);
755+
let op = self.codegen_operand(bx, &fields[field_idx]);
756+
let values = op.val.immediates_or_place().left_or_else(|p| {
757+
bug!("Field {field_idx:?} is {p:?} making {layout:?}");
758+
});
759+
inputs.extend(values);
760+
let scalars = self.value_kind(op.layout).scalars().unwrap();
761+
input_scalars.extend(scalars);
762+
}
763+
764+
let output_scalars = self.value_kind(layout).scalars().unwrap();
765+
itertools::izip!(&mut inputs, input_scalars, output_scalars).for_each(
766+
|(v, in_s, out_s)| {
767+
if in_s != out_s {
768+
// We have to be really careful about bool here, because
769+
// `(bool,)` stays i1 but `Cell<bool>` becomes i8.
770+
*v = bx.from_immediate(*v);
771+
*v = bx.to_immediate_scalar(*v, out_s);
772+
}
773+
},
774+
);
775+
776+
let val = OperandValue::from_immediates(inputs);
777+
OperandRef { val, layout }
744778
}
745779
mir::Rvalue::ShallowInitBox(ref operand, content_ty) => {
746780
let operand = self.codegen_operand(bx, operand);
@@ -1047,14 +1081,29 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10471081
mir::Rvalue::ThreadLocalRef(_) |
10481082
mir::Rvalue::Use(..) => // (*)
10491083
true,
1050-
// This always produces a `ty::RawPtr`, so will be Immediate or Pair
1051-
mir::Rvalue::Aggregate(box AggregateKind::RawPtr(..), ..) => true,
1052-
mir::Rvalue::Repeat(..) |
1053-
mir::Rvalue::Aggregate(..) => {
1084+
// Arrays are always aggregates, so it's not worth checking anything here.
1085+
// (If it's really `[(); N]` or `[T; 0]` and we use the place path, fine.)
1086+
mir::Rvalue::Repeat(..) => false,
1087+
mir::Rvalue::Aggregate(ref kind, _) => {
1088+
let allowed_kind = match **kind {
1089+
// This always produces a `ty::RawPtr`, so will be Immediate or Pair
1090+
mir::AggregateKind::RawPtr(..) => true,
1091+
mir::AggregateKind::Array(..) => false,
1092+
mir::AggregateKind::Tuple => true,
1093+
mir::AggregateKind::Adt(def_id, ..) => {
1094+
let adt_def = self.cx.tcx().adt_def(def_id);
1095+
adt_def.is_struct() && !adt_def.repr().simd()
1096+
}
1097+
mir::AggregateKind::Closure(..) => true,
1098+
// FIXME: Can we do this for simple coroutines too?
1099+
mir::AggregateKind::Coroutine(..) | mir::AggregateKind::CoroutineClosure(..) => false,
1100+
};
1101+
allowed_kind && {
10541102
let ty = rvalue.ty(self.mir, self.cx.tcx());
10551103
let ty = self.monomorphize(ty);
1056-
// For ZST this can be `OperandValueKind::ZeroSized`.
1057-
self.cx.spanned_layout_of(ty, span).is_zst()
1104+
let layout = self.cx.spanned_layout_of(ty, span);
1105+
!self.cx.is_backend_ref(layout)
1106+
}
10581107
}
10591108
}
10601109

@@ -1096,3 +1145,14 @@ enum OperandValueKind {
10961145
Pair(abi::Scalar, abi::Scalar),
10971146
ZeroSized,
10981147
}
1148+
1149+
impl OperandValueKind {
1150+
fn scalars(self) -> Option<ArrayVec<abi::Scalar, 2>> {
1151+
Some(match self {
1152+
OperandValueKind::ZeroSized => ArrayVec::new(),
1153+
OperandValueKind::Immediate(a) => ArrayVec::from_iter([a]),
1154+
OperandValueKind::Pair(a, b) => [a, b].into(),
1155+
OperandValueKind::Ref => return None,
1156+
})
1157+
}
1158+
}
+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//@ compile-flags: -O -C no-prepopulate-passes -Z randomize-layout=no
2+
3+
#![crate_type = "lib"]
4+
5+
#[repr(transparent)]
6+
pub struct Transparent32(u32);
7+
8+
// CHECK: i32 @make_transparent(i32 noundef %x)
9+
#[no_mangle]
10+
pub fn make_transparent(x: u32) -> Transparent32 {
11+
// CHECK-NOT: alloca
12+
// CHECK: ret i32 %x
13+
let a = Transparent32(x);
14+
a
15+
}
16+
17+
// CHECK: i32 @make_closure(i32 noundef %x)
18+
#[no_mangle]
19+
pub fn make_closure(x: i32) -> impl Fn(i32) -> i32 {
20+
// CHECK-NOT: alloca
21+
// CHECK: ret i32 %x
22+
move |y| x + y
23+
}
24+
25+
#[repr(transparent)]
26+
pub struct TransparentPair((), (u16, u16), ());
27+
28+
// CHECK: { i16, i16 } @make_transparent_pair(i16 noundef %x.0, i16 noundef %x.1)
29+
#[no_mangle]
30+
pub fn make_transparent_pair(x: (u16, u16)) -> TransparentPair {
31+
// CHECK-NOT: alloca
32+
// CHECK: %[[TEMP0:.+]] = insertvalue { i16, i16 } poison, i16 %x.0, 0
33+
// CHECK: %[[TEMP1:.+]] = insertvalue { i16, i16 } %[[TEMP0]], i16 %x.1, 1
34+
// CHECK: ret { i16, i16 } %[[TEMP1]]
35+
let a = TransparentPair((), x, ());
36+
a
37+
}
38+
39+
// CHECK-LABEL: { i32, i32 } @make_2_tuple(i32 noundef %x)
40+
#[no_mangle]
41+
pub fn make_2_tuple(x: u32) -> (u32, u32) {
42+
// CHECK-NOT: alloca
43+
// CHECK: %[[TEMP0:.+]] = insertvalue { i32, i32 } poison, i32 %x, 0
44+
// CHECK: %[[TEMP1:.+]] = insertvalue { i32, i32 } %[[TEMP0]], i32 %x, 1
45+
// CHECK: ret { i32, i32 } %[[TEMP1]]
46+
let pair = (x, x);
47+
pair
48+
}
49+
50+
// CHECK-LABEL: i8 @make_cell_of_bool(i1 noundef zeroext %b)
51+
#[no_mangle]
52+
pub fn make_cell_of_bool(b: bool) -> std::cell::Cell<bool> {
53+
// CHECK: %[[BYTE:.+]] = zext i1 %b to i8
54+
// CHECK: ret i8 %[[BYTE]]
55+
std::cell::Cell::new(b)
56+
}
57+
58+
// CHECK-LABLE: { i8, i16 } @make_cell_of_bool_and_short(i1 noundef zeroext %b, i16 noundef %s)
59+
#[no_mangle]
60+
pub fn make_cell_of_bool_and_short(b: bool, s: u16) -> std::cell::Cell<(bool, u16)> {
61+
// CHECK-NOT: alloca
62+
// CHECK: %[[BYTE:.+]] = zext i1 %b to i8
63+
// CHECK: %[[TEMP0:.+]] = insertvalue { i8, i16 } poison, i8 %[[BYTE]], 0
64+
// CHECK: %[[TEMP1:.+]] = insertvalue { i8, i16 } %[[TEMP0]], i16 %s, 1
65+
// CHECK: ret { i8, i16 } %[[TEMP1]]
66+
std::cell::Cell::new((b, s))
67+
}
68+
69+
// CHECK-LABEL: { i1, i1 } @make_tuple_of_bools(i1 noundef zeroext %a, i1 noundef zeroext %b)
70+
#[no_mangle]
71+
pub fn make_tuple_of_bools(a: bool, b: bool) -> (bool, bool) {
72+
// CHECK-NOT: alloca
73+
// CHECK: %[[TEMP0:.+]] = insertvalue { i1, i1 } poison, i1 %a, 0
74+
// CHECK: %[[TEMP1:.+]] = insertvalue { i1, i1 } %[[TEMP0]], i1 %b, 1
75+
// CHECK: ret { i1, i1 } %[[TEMP1]]
76+
(a, b)
77+
}
78+
79+
pub struct Struct0();
80+
81+
// CHECK-LABEL: void @make_struct_0()
82+
#[no_mangle]
83+
pub fn make_struct_0() -> Struct0 {
84+
// CHECK: ret void
85+
let s = Struct0();
86+
s
87+
}
88+
89+
pub struct Struct1(i32);
90+
91+
// CHECK-LABEL: i32 @make_struct_1(i32 noundef %a)
92+
#[no_mangle]
93+
pub fn make_struct_1(a: i32) -> Struct1 {
94+
// CHECK: ret i32 %a
95+
let s = Struct1(a);
96+
s
97+
}
98+
99+
pub struct Struct2Asc(i16, i64);
100+
101+
// CHECK-LABEL: { i64, i16 } @make_struct_2_asc(i16 noundef %a, i64 noundef %b)
102+
#[no_mangle]
103+
pub fn make_struct_2_asc(a: i16, b: i64) -> Struct2Asc {
104+
// CHECK-NOT: alloca
105+
// CHECK: %[[TEMP0:.+]] = insertvalue { i64, i16 } poison, i64 %b, 0
106+
// CHECK: %[[TEMP1:.+]] = insertvalue { i64, i16 } %[[TEMP0]], i16 %a, 1
107+
// CHECK: ret { i64, i16 } %[[TEMP1]]
108+
let s = Struct2Asc(a, b);
109+
s
110+
}
111+
112+
pub struct Struct2Desc(i64, i16);
113+
114+
// CHECK-LABEL: { i64, i16 } @make_struct_2_desc(i64 noundef %a, i16 noundef %b)
115+
#[no_mangle]
116+
pub fn make_struct_2_desc(a: i64, b: i16) -> Struct2Desc {
117+
// CHECK-NOT: alloca
118+
// CHECK: %[[TEMP0:.+]] = insertvalue { i64, i16 } poison, i64 %a, 0
119+
// CHECK: %[[TEMP1:.+]] = insertvalue { i64, i16 } %[[TEMP0]], i16 %b, 1
120+
// CHECK: ret { i64, i16 } %[[TEMP1]]
121+
let s = Struct2Desc(a, b);
122+
s
123+
}

0 commit comments

Comments
 (0)