Skip to content

Commit e9acbd9

Browse files
committed
Auto merge of #147827 - saethlin:maybeuninit-codegen2, r=scottmcm
Fix MaybeUninit codegen using GVN This is an alternative to #142837, based on #146355 (comment). The general approach I took here is to aggressively propagate anything that is entirely uninitialized. GVN generally takes the approach of only synthesizing small types, but we need to generate large consts to fix the codegen issue. I also added a special case to MIR dumps for this where now an entirely uninit const is printed as `const <uninit>`, because otherwise we end up with extremely verbose dumps of the new consts. After GVN though, we still end up with a lot of MIR that looks like this: ``` StorageLive(_1); _1 = const <uninit>; _2 = &raw mut _1; ``` Which will break tests/codegen-llvm/maybeuninit-rvo.rs with the naive lowering. I think the ideal fix here is to somehow omit these `_1 = const <uninit>` assignments that come directly after a StorageLive, but I'm not sure how to do that. For now at least, ignoring such assignments (even if they don't come right after a StorageLive) in codegen seems to work. Note that since GVN is based on synthesizing a `ConstValue` which has a defined layout, this scenario still gets deoptimized by LLVM. ```rust #![feature(rustc_attrs)] #![crate_type = "lib"] use std::mem::MaybeUninit; #[unsafe(no_mangle)] pub fn oof() -> [[MaybeUninit<u8>; 8]; 8] { #[rustc_no_mir_inline] pub fn inner<T: Copy>() -> [[MaybeUninit<T>; 8]; 8] { [[MaybeUninit::uninit(); 8]; 8] } inner() } ``` This case can be handled correctly if enough inlining has happened, or it could be handled by post-mono GVN. Synthesizing `UnevaluatedConst` or some other special kind of const seems dubious.
2 parents 122cbd0 + 1a4852c commit e9acbd9

File tree

11 files changed

+112
-17
lines changed

11 files changed

+112
-17
lines changed

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
2424
) {
2525
match *rvalue {
2626
mir::Rvalue::Use(ref operand) => {
27+
if let mir::Operand::Constant(const_op) = operand {
28+
let val = self.eval_mir_constant(&const_op);
29+
if val.all_bytes_uninit(self.cx.tcx()) {
30+
return;
31+
}
32+
}
2733
let cg_operand = self.codegen_operand(bx, operand);
2834
// Crucially, we do *not* use `OperandValue::Ref` for types with
2935
// `BackendRepr::Scalar | BackendRepr::ScalarPair`. This ensures we match the MIR

compiler/rustc_const_eval/src/interpret/operand.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,10 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
522522
pub(super) fn op(&self) -> &Operand<Prov> {
523523
&self.op
524524
}
525+
526+
pub fn is_immediate_uninit(&self) -> bool {
527+
matches!(self.op, Operand::Immediate(Immediate::Uninit))
528+
}
525529
}
526530

527531
impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> {

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,13 @@ fn pretty_print_const_value_tcx<'tcx>(
18691869
return Ok(());
18701870
}
18711871

1872+
// Printing [MaybeUninit<u8>::uninit(); N] or any other aggregate where all fields are uninit
1873+
// becomes very verbose. This special case makes the dump terse and clear.
1874+
if ct.all_bytes_uninit(tcx) {
1875+
fmt.write_str("<uninit>")?;
1876+
return Ok(());
1877+
}
1878+
18721879
let u8_type = tcx.types.u8;
18731880
match (ct, ty.kind()) {
18741881
// Byte/string slices, printed as (byte) string literals.

compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,19 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
570570
_ if ty.is_zst() => ImmTy::uninit(ty).into(),
571571

572572
Opaque(_) => return None,
573-
// Do not bother evaluating repeat expressions. This would uselessly consume memory.
574-
Repeat(..) => return None,
575573

574+
// In general, evaluating repeat expressions just consumes a lot of memory.
575+
// But in the special case that the element is just Immediate::Uninit, we can evaluate
576+
// it without extra memory! If we don't propagate uninit values like this, LLVM can get
577+
// very confused: https://github.com/rust-lang/rust/issues/139355
578+
Repeat(value, _count) => {
579+
let value = self.eval_to_const(value)?;
580+
if value.is_immediate_uninit() {
581+
ImmTy::uninit(ty).into()
582+
} else {
583+
return None;
584+
}
585+
}
576586
Constant { ref value, disambiguator: _ } => {
577587
self.ecx.eval_mir_constant(value, DUMMY_SP, None).discard_err()?
578588
}
@@ -608,8 +618,12 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
608618
}
609619
Union(active_field, field) => {
610620
let field = self.eval_to_const(field)?;
611-
if matches!(ty.backend_repr, BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..))
612-
{
621+
if field.layout.layout.is_zst() {
622+
ImmTy::from_immediate(Immediate::Uninit, ty).into()
623+
} else if matches!(
624+
ty.backend_repr,
625+
BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..)
626+
) {
613627
let dest = self.ecx.allocate(ty, MemoryKind::Stack).discard_err()?;
614628
let field_dest = self.ecx.project_field(&dest, active_field).discard_err()?;
615629
self.ecx.copy_op(field, &field_dest).discard_err()?;
@@ -1695,7 +1709,11 @@ fn op_to_prop_const<'tcx>(
16951709

16961710
// Do not synthetize too large constants. Codegen will just memcpy them, which we'd like to
16971711
// avoid.
1698-
if !matches!(op.layout.backend_repr, BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..)) {
1712+
// But we *do* want to synthesize any size constant if it is entirely uninit because that
1713+
// benefits codegen, which has special handling for them.
1714+
if !op.is_immediate_uninit()
1715+
&& !matches!(op.layout.backend_repr, BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..))
1716+
{
16991717
return None;
17001718
}
17011719

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This is a regression test for https://github.com/rust-lang/rust/issues/139355
2+
3+
//@ compile-flags: -Copt-level=3
4+
5+
#![crate_type = "lib"]
6+
7+
use std::mem::MaybeUninit;
8+
9+
#[no_mangle]
10+
pub fn create_uninit_array() -> [[MaybeUninit<u8>; 4]; 200] {
11+
// CHECK-LABEL: create_uninit_array
12+
// CHECK-NEXT: start:
13+
// CHECK-NEXT: ret void
14+
[[MaybeUninit::<u8>::uninit(); 4]; 200]
15+
}

tests/codegen-llvm/uninit-consts.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,19 @@ pub struct PartiallyUninit {
1111
y: MaybeUninit<[u8; 10]>,
1212
}
1313

14-
// CHECK: [[FULLY_UNINIT:@.*]] = private unnamed_addr constant [10 x i8] undef
15-
1614
// CHECK: [[PARTIALLY_UNINIT:@.*]] = private unnamed_addr constant <{ [4 x i8], [12 x i8] }> <{ [4 x i8] c"{{\\EF\\BE\\AD\\DE|\\DE\\AD\\BE\\EF}}", [12 x i8] undef }>, align 4
1715

1816
// This shouldn't contain undef, since it contains more chunks
1917
// than the default value of uninit_const_chunk_threshold.
2018
// CHECK: [[UNINIT_PADDING_HUGE:@.*]] = private unnamed_addr constant [32768 x i8] c"{{.+}}", align 4
2119

22-
// CHECK: [[FULLY_UNINIT_HUGE:@.*]] = private unnamed_addr constant [16384 x i8] undef
23-
2420
// CHECK-LABEL: @fully_uninit
2521
#[no_mangle]
2622
pub const fn fully_uninit() -> MaybeUninit<[u8; 10]> {
2723
const M: MaybeUninit<[u8; 10]> = MaybeUninit::uninit();
28-
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align 1 %_0, ptr align 1 {{.*}}[[FULLY_UNINIT]]{{.*}}, i{{(32|64)}} 10, i1 false)
24+
// returning uninit doesn't need to do anything to the return place at all
25+
// CHECK-NEXT: start:
26+
// CHECK-NEXT: ret void
2927
M
3028
}
3129

@@ -49,6 +47,8 @@ pub const fn uninit_padding_huge() -> [(u32, u8); 4096] {
4947
#[no_mangle]
5048
pub const fn fully_uninit_huge() -> MaybeUninit<[u32; 4096]> {
5149
const F: MaybeUninit<[u32; 4096]> = MaybeUninit::uninit();
52-
// CHECK: call void @llvm.memcpy.{{.+}}(ptr align 4 %_0, ptr align 4 {{.*}}[[FULLY_UNINIT_HUGE]]{{.*}}, i{{(32|64)}} 16384, i1 false)
50+
// returning uninit doesn't need to do anything to the return place at all
51+
// CHECK-NEXT: start:
52+
// CHECK-NEXT: ret void
5353
F
5454
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//@ compile-flags: -O
2+
3+
use std::mem::MaybeUninit;
4+
5+
// EMIT_MIR maybe_uninit.u8_array.GVN.diff
6+
pub fn u8_array() -> [MaybeUninit<u8>; 8] {
7+
// CHECK: fn u8_array(
8+
// CHECK: _0 = const <uninit>;
9+
[MaybeUninit::uninit(); 8]
10+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
- // MIR for `u8_array` before GVN
2+
+ // MIR for `u8_array` after GVN
3+
4+
fn u8_array() -> [MaybeUninit<u8>; 8] {
5+
let mut _0: [std::mem::MaybeUninit<u8>; 8];
6+
let mut _1: std::mem::MaybeUninit<u8>;
7+
scope 1 (inlined MaybeUninit::<u8>::uninit) {
8+
}
9+
10+
bb0: {
11+
StorageLive(_1);
12+
- _1 = MaybeUninit::<u8> { uninit: const () };
13+
- _0 = [move _1; 8];
14+
+ _1 = const <uninit>;
15+
+ _0 = const <uninit>;
16+
StorageDead(_1);
17+
return;
18+
}
19+
+ }
20+
+
21+
+ ALLOC0 (size: 8, align: 1) {
22+
+ __ __ __ __ __ __ __ __ │ ░░░░░░░░
23+
+ }
24+
+
25+
+ ALLOC1 (size: 1, align: 1) {
26+
+ __ │ ░
27+
}
28+

tests/mir-opt/const_prop/transmute.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ pub unsafe fn invalid_bool() -> bool {
4343
// EMIT_MIR transmute.undef_union_as_integer.GVN.diff
4444
pub unsafe fn undef_union_as_integer() -> u32 {
4545
// CHECK-LABEL: fn undef_union_as_integer(
46-
// CHECK: _1 = const Union32
47-
// CHECK: _0 = const {{.*}}: u32;
46+
// CHECK: _0 = const <uninit>;
4847
union Union32 {
4948
value: u32,
5049
unit: (),

tests/mir-opt/const_prop/transmute.undef_union_as_integer.GVN.32bit.diff

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,20 @@
1212
- _2 = ();
1313
- _1 = Union32 { value: move _2 };
1414
+ _2 = const ();
15-
+ _1 = const Union32 {{ value: Indirect { alloc_id: ALLOC0, offset: Size(0 bytes) }: u32, unit: () }};
15+
+ _1 = const <uninit>;
1616
StorageDead(_2);
1717
- _0 = move _1 as u32 (Transmute);
18-
+ _0 = const Indirect { alloc_id: ALLOC0, offset: Size(0 bytes) }: u32;
18+
+ _0 = const <uninit>;
1919
StorageDead(_1);
2020
return;
2121
}
2222
+ }
2323
+
2424
+ ALLOC0 (size: 4, align: 4) {
25+
+ __ __ __ __ │ ░░░░
26+
+ }
27+
+
28+
+ ALLOC1 (size: 4, align: 4) {
2529
+ __ __ __ __ │ ░░░░
2630
}
2731

0 commit comments

Comments
 (0)