Skip to content

Commit e7795af

Browse files
committed
Auto merge of #157797 - glandium:uninit, r=oli-obk
codegen: skip stores for entirely-uninit constant aggregate fields MIR GVN (since #147827) propagates MaybeUninit::uninit() as `const <uninit>` in aggregate constructions. Without this fix, codegen would emit a memcpy from an `[N x i8] undef` global for each such field, which LLVM materializes as zero-initialization. This mirrors the existing `all_bytes_uninit` skip already present for `Rvalue::Use` (added in #147827) into the `Rvalue::Aggregate` field loop. Fixes: #157743
2 parents 6f72b5d + ed9de1e commit e7795af

10 files changed

Lines changed: 84 additions & 34 deletions

File tree

compiler/rustc_codegen_gcc/src/intrinsic/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc
629629

630630
for arg in args {
631631
match arg.val {
632-
OperandValue::ZeroSized => {}
632+
OperandValue::ZeroSized | OperandValue::Uninit => {}
633633
OperandValue::Immediate(_) => call_args.push(arg.immediate()),
634634
OperandValue::Pair(a, b) => {
635635
call_args.push(a);

compiler/rustc_codegen_llvm/src/intrinsic.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
962962
for arg in args {
963963
match arg.val {
964964
OperandValue::ZeroSized => {}
965+
OperandValue::Uninit => {}
965966
OperandValue::Immediate(a) => llargs.push(a),
966967
OperandValue::Pair(a, b) => {
967968
llargs.push(a);
@@ -1939,7 +1940,7 @@ fn get_args_from_tuple<'ll, 'tcx>(
19391940
result
19401941
}
19411942

1942-
OperandValue::ZeroSized => vec![],
1943+
OperandValue::ZeroSized | OperandValue::Uninit => vec![],
19431944
}
19441945
}
19451946

compiler/rustc_codegen_ssa/src/base.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ pub(crate) fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
287287
let (base, info) = match bx.load_operand(src).val {
288288
OperandValue::Pair(base, info) => unsize_ptr(bx, base, src_ty, dst_ty, Some(info)),
289289
OperandValue::Immediate(base) => unsize_ptr(bx, base, src_ty, dst_ty, None),
290-
OperandValue::Ref(..) | OperandValue::ZeroSized => bug!(),
290+
OperandValue::Ref(..) | OperandValue::ZeroSized | OperandValue::Uninit => bug!(),
291291
};
292292
OperandValue::Pair(base, info).store(bx, dst);
293293
}

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_target::callconv::{ArgAbi, ArgAttributes, CastTarget, FnAbi, PassMode}
2121
use tracing::{debug, info};
2222

2323
use super::operand::OperandRef;
24-
use super::operand::OperandValue::{self, Immediate, Pair, Ref, ZeroSized};
24+
use super::operand::OperandValue::{self, Immediate, Pair, Ref, Uninit, ZeroSized};
2525
use super::place::{PlaceRef, PlaceValue};
2626
use super::{CachedLlbb, FunctionCx, LocalRef};
2727
use crate::base::{self, is_call_from_compiler_builtins_to_upstream_monomorphization};
@@ -611,6 +611,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
611611
place_val.llval
612612
}
613613
ZeroSized => bug!("ZST return value shouldn't be in PassMode::Cast"),
614+
OperandValue::Uninit => {
615+
bug!("uninit return value shouldn't be in PassMode::Cast")
616+
}
614617
};
615618

616619
if self.fn_abi.conv == CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) {
@@ -1800,7 +1803,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18001803

18011804
// Force by-ref if we have to load through a cast pointer.
18021805
let (mut llval, align, by_ref) = match op.val {
1803-
Immediate(_) | Pair(..) => match arg.mode {
1806+
Immediate(_) | Pair(..) | Uninit => match arg.mode {
18041807
PassMode::Indirect { attrs, .. } => {
18051808
// Indirect argument may have higher alignment requirements than the type's
18061809
// alignment. This can happen, e.g. when passing types with <4 byte alignment
@@ -1820,7 +1823,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
18201823
op.store_with_annotation(bx, scratch);
18211824
(scratch.val.llval, scratch.val.align, true)
18221825
}
1823-
PassMode::Direct(_) => (op.immediate(), arg.layout.align.abi, false),
1826+
PassMode::Direct(_) => {
1827+
if let Uninit = op.val {
1828+
let ibty = bx.cx().immediate_backend_type(arg.layout);
1829+
(bx.cx().const_undef(ibty), arg.layout.align.abi, false)
1830+
} else {
1831+
(op.immediate(), arg.layout.align.abi, false)
1832+
}
1833+
}
18241834
PassMode::Ignore | PassMode::Pair(..) => unreachable!("handled above"),
18251835
},
18261836
Ref(op_place_val) => match arg.mode {

compiler/rustc_codegen_ssa/src/mir/constant.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_middle::{bug, mir, span_bug};
66

77
use super::FunctionCx;
88
use crate::diagnostics;
9-
use crate::mir::operand::OperandRef;
9+
use crate::mir::operand::{OperandRef, OperandValue};
1010
use crate::traits::*;
1111

1212
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
@@ -17,6 +17,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
1717
) -> OperandRef<'tcx, Bx::Value> {
1818
let val = self.eval_mir_constant(constant);
1919
let ty = self.monomorphize(constant.ty());
20+
if val.all_bytes_uninit(self.cx.tcx()) {
21+
let layout = bx.layout_of(ty);
22+
return OperandRef { val: OperandValue::Uninit, layout, move_annotation: None };
23+
}
2024
OperandRef::from_const(bx, val, ty)
2125
}
2226

compiler/rustc_codegen_ssa/src/mir/debuginfo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
390390
bx.set_var_name(a, &(name.clone() + ".0"));
391391
bx.set_var_name(b, &(name.clone() + ".1"));
392392
}
393-
OperandValue::ZeroSized => {
393+
OperandValue::ZeroSized | OperandValue::Uninit => {
394394
// These never have a value to talk about
395395
}
396396
},

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ pub enum OperandValue<V> {
8686
/// `is_zst` on its `Layout` returns `true`. Note however that
8787
/// these values can still require alignment.
8888
ZeroSized,
89+
/// A value for which all bytes are entirely uninitialized.
90+
///
91+
/// Storing this value is a no-op; it propagates through field extraction.
92+
/// Used to avoid emitting memcpys from uninit globals (which LLVM may
93+
/// otherwise materialize as zero-fills) for `const <uninit>` operands.
94+
Uninit,
8995
}
9096

9197
impl<V: CodegenObject> OperandValue<V> {
@@ -95,7 +101,7 @@ impl<V: CodegenObject> OperandValue<V> {
95101
match self {
96102
OperandValue::Immediate(llptr) => Some((llptr, None)),
97103
OperandValue::Pair(llptr, llextra) => Some((llptr, Some(llextra))),
98-
OperandValue::Ref(_) | OperandValue::ZeroSized => None,
104+
OperandValue::Ref(_) | OperandValue::ZeroSized | OperandValue::Uninit => None,
99105
}
100106
}
101107

@@ -123,6 +129,7 @@ impl<V: CodegenObject> OperandValue<V> {
123129
#[must_use]
124130
pub(crate) fn is_expected_variant_for_type<'tcx>(&self, ty: TyAndLayout<'tcx>) -> bool {
125131
match (self, ty.backend_repr) {
132+
(OperandValue::Uninit, _) => true,
126133
(OperandValue::ZeroSized, BackendRepr::Memory { .. }) => ty.is_zst(),
127134
(OperandValue::Ref(_), BackendRepr::Memory { .. }) => !ty.is_zst(),
128135
(
@@ -397,7 +404,9 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
397404
);
398405
}
399406

400-
let val = if field.is_zst() {
407+
let val = if let OperandValue::Uninit = self.val {
408+
OperandValue::Uninit
409+
} else if field.is_zst() {
401410
OperandValue::ZeroSized
402411
} else if field.size == self.layout.size {
403412
assert_eq!(offset.bytes(), 0);
@@ -496,6 +505,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
496505
// Read the tag/niche-encoded discriminant from memory.
497506
let tag_op = match self.val {
498507
OperandValue::ZeroSized => bug!(),
508+
OperandValue::Uninit => return bx.cx().const_poison(cast_to),
499509
OperandValue::Immediate(_) | OperandValue::Pair(_, _) => {
500510
self.extract_field(fx, bx, tag_field.as_usize())
501511
}
@@ -778,12 +788,15 @@ impl<'a, 'tcx, V: CodegenObject> OperandRefBuilder<'tcx, V> {
778788
field: FieldIdx,
779789
field_operand: OperandRef<'tcx, V>,
780790
) {
781-
if let OperandValue::ZeroSized = field_operand.val {
791+
if matches!(field_operand.val, OperandValue::ZeroSized | OperandValue::Uninit) {
782792
// A ZST never adds any state, so just ignore it.
783793
// This special-casing is worth it because of things like
784794
// `Result<!, !>` where `Ok(never)` is legal to write,
785795
// but the type shows as FieldShape::Primitive so we can't
786796
// actually look at the layout for the field being set.
797+
//
798+
// Likewise, an uninit field does not contribute any value;
799+
// the builder's unset slots will produce `const_undef` in `build()`.
787800
return;
788801
}
789802

@@ -1019,6 +1032,10 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue<V> {
10191032
// Avoid generating stores of zero-sized values, because the only way to have a
10201033
// zero-sized value is through `undef`/`poison`, and the store itself is useless.
10211034
}
1035+
OperandValue::Uninit => {
1036+
// Storing an entirely uninit value is a no-op: the destination is left
1037+
// uninitialized, which is valid since the value itself is uninit.
1038+
}
10221039
OperandValue::Ref(val) => {
10231040
assert!(dest.layout.is_sized(), "cannot directly store unsized values");
10241041
if val.llextra.is_some() {

compiler/rustc_codegen_ssa/src/mir/retag.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
298298
builder.update_imm(offset, fst);
299299
builder.update_imm(offset + Size::from_bytes(1), snd)
300300
}
301+
OperandValue::Uninit => {
302+
unreachable!("load_operand never produces Uninit")
303+
}
301304
}
302305
}
303306
}

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
100100
) {
101101
match *rvalue {
102102
mir::Rvalue::Use(ref operand, with_retag) => {
103-
if let mir::Operand::Constant(const_op) = operand {
104-
let val = self.eval_mir_constant(&const_op);
105-
if val.all_bytes_uninit(self.cx.tcx()) {
106-
return;
107-
}
108-
}
109103
let cg_operand = self.codegen_operand(bx, operand);
110104
// Crucially, we do *not* use `OperandValue::Ref` for types with
111105
// `BackendRepr::Scalar | BackendRepr::ScalarPair`. This ensures we match the MIR
@@ -176,6 +170,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
176170
OperandValue::ZeroSized => {
177171
bug!("unsized coercion on a ZST rvalue");
178172
}
173+
OperandValue::Uninit => {
174+
bug!("unsized coercion on an uninit rvalue");
175+
}
179176
}
180177
}
181178

@@ -194,25 +191,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
194191
return;
195192
}
196193

197-
// When the element is a const with all bytes uninit, emit a single memset that
198-
// writes undef to the entire destination.
199-
if let mir::Operand::Constant(const_op) = elem {
200-
let val = self.eval_mir_constant(const_op);
201-
if val.all_bytes_uninit(self.cx.tcx()) {
202-
let size = bx.const_usize(dest.layout.size.bytes());
203-
bx.memset(
204-
dest.val.llval,
205-
bx.const_undef(bx.type_i8()),
206-
size,
207-
dest.val.align,
208-
MemFlags::empty(),
209-
);
210-
return;
211-
}
212-
}
213-
214194
let cg_elem = self.codegen_operand(bx, elem);
215195

196+
if let OperandValue::Uninit = cg_elem.val {
197+
return;
198+
}
199+
216200
let try_init_all_same = |bx: &mut Bx, v| {
217201
let start = dest.val.llval;
218202
let size = bx.const_usize(dest.layout.size.bytes());
@@ -372,6 +356,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
372356
let cx = bx.cx();
373357
match (operand.val, operand.layout.backend_repr, cast.backend_repr) {
374358
_ if cast.is_zst() => OperandValue::ZeroSized,
359+
(OperandValue::Uninit, _, _) => OperandValue::Uninit,
375360
(OperandValue::Ref(source_place_val), abi::BackendRepr::Memory { .. }, _) => {
376361
assert_eq!(source_place_val.llextra, None);
377362
// The existing alignment is part of `source_place_val`,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Regression test for https://github.com/rust-lang/rust/issues/157743
2+
//
3+
// At opt-level >= 1, MIR GVN inlines MaybeUninit::uninit() and propagates the
4+
// result as `const <uninit>` in aggregate constructions. Without the fix, this
5+
// caused codegen to emit a memcpy from an `[N x i8] undef` global constant for
6+
// the uninit field, which LLVM would materialize as zero-initialization.
7+
//
8+
// The fix skips emitting any IR for entirely-uninit constant aggregate fields.
9+
10+
//@ compile-flags: -C no-prepopulate-passes -C opt-level=2
11+
12+
#![crate_type = "lib"]
13+
14+
use std::mem::MaybeUninit;
15+
16+
pub struct Inner {
17+
cap: usize,
18+
data: MaybeUninit<[u64; 2]>,
19+
}
20+
21+
// CHECK-LABEL: @make_inner
22+
// The non-uninit field must be stored.
23+
// CHECK: store i{{(32|64)}}
24+
// The entirely-uninit `data` field must not cause a memcpy from an undef global.
25+
// CHECK-NOT: call void @llvm.memcpy
26+
// CHECK: ret void
27+
#[no_mangle]
28+
pub fn make_inner(cap: usize) -> Inner {
29+
Inner { cap, data: MaybeUninit::uninit() }
30+
}

0 commit comments

Comments
 (0)