Skip to content

Commit 4d635fd

Browse files
use undef for uninitialized bytes in constants
1 parent 7b0e554 commit 4d635fd

File tree

7 files changed

+202
-21
lines changed

7 files changed

+202
-21
lines changed

compiler/rustc_codegen_llvm/src/consts.rs

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,70 @@ use rustc_codegen_ssa::traits::*;
1111
use rustc_hir::def_id::DefId;
1212
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
1313
use rustc_middle::mir::interpret::{
14-
read_target_uint, Allocation, ErrorHandled, GlobalAlloc, Pointer, Scalar as InterpScalar,
14+
read_target_uint, Allocation, ErrorHandled, GlobalAlloc, InitChunk, Pointer,
15+
Scalar as InterpScalar,
1516
};
1617
use rustc_middle::mir::mono::MonoItem;
1718
use rustc_middle::ty::{self, Instance, Ty};
1819
use rustc_middle::{bug, span_bug};
1920
use rustc_target::abi::{
2021
AddressSpace, Align, HasDataLayout, LayoutOf, Primitive, Scalar, Size, WrappingRange,
2122
};
23+
use std::ops::Range;
2224
use tracing::debug;
2325

2426
pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll Value {
2527
let mut llvals = Vec::with_capacity(alloc.relocations().len() + 1);
2628
let dl = cx.data_layout();
2729
let pointer_size = dl.pointer_size.bytes() as usize;
2830

31+
// Note: this function may call `inspect_with_uninit_and_ptr_outside_interpreter`,
32+
// so `range` must be within the bounds of `alloc` and not within a relocation.
33+
fn append_chunks_of_init_and_uninit_bytes<'ll, 'a, 'b>(
34+
llvals: &mut Vec<&'ll Value>,
35+
cx: &'a CodegenCx<'ll, 'b>,
36+
alloc: &'a Allocation,
37+
range: Range<usize>,
38+
) {
39+
/// Allocations larger than this will only be codegen'd as entirely initialized or entirely undef.
40+
/// This avoids compile time regressions when an alloc would have many chunks,
41+
/// e.g. for `[(u64, u8); N]`, which has undef padding in each element.
42+
const MAX_PARTIALLY_UNDEF_SIZE: usize = 1024;
43+
44+
let mut chunks = alloc
45+
.init_mask()
46+
.range_as_init_chunks(Size::from_bytes(range.start), Size::from_bytes(range.end));
47+
48+
let chunk_to_llval = move |chunk| match chunk {
49+
InitChunk::Init(range) => {
50+
let range = (range.start.bytes() as usize)..(range.end.bytes() as usize);
51+
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range);
52+
cx.const_bytes(bytes)
53+
}
54+
InitChunk::Uninit(range) => {
55+
let len = range.end.bytes() - range.start.bytes();
56+
cx.const_undef(cx.type_array(cx.type_i8(), len))
57+
}
58+
};
59+
60+
if range.len() > MAX_PARTIALLY_UNDEF_SIZE {
61+
let llval = match (chunks.next(), chunks.next()) {
62+
(Some(chunk), None) => {
63+
// exactly one chunk, either fully init or fully uninit
64+
chunk_to_llval(chunk)
65+
}
66+
_ => {
67+
// partially uninit
68+
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range);
69+
cx.const_bytes(bytes)
70+
}
71+
};
72+
llvals.push(llval);
73+
} else {
74+
llvals.extend(chunks.map(chunk_to_llval));
75+
}
76+
}
77+
2978
let mut next_offset = 0;
3079
for &(offset, alloc_id) in alloc.relocations().iter() {
3180
let offset = offset.bytes();
@@ -34,12 +83,8 @@ pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll
3483
if offset > next_offset {
3584
// This `inspect` is okay since we have checked that it is not within a relocation, it
3685
// is within the bounds of the allocation, and it doesn't affect interpreter execution
37-
// (we inspect the result after interpreter execution). Any undef byte is replaced with
38-
// some arbitrary byte value.
39-
//
40-
// FIXME: relay undef bytes to codegen as undef const bytes
41-
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(next_offset..offset);
42-
llvals.push(cx.const_bytes(bytes));
86+
// (we inspect the result after interpreter execution).
87+
append_chunks_of_init_and_uninit_bytes(&mut llvals, cx, alloc, next_offset..offset);
4388
}
4489
let ptr_offset = read_target_uint(
4590
dl.endian,
@@ -70,12 +115,8 @@ pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll
70115
let range = next_offset..alloc.len();
71116
// This `inspect` is okay since we have check that it is after all relocations, it is
72117
// within the bounds of the allocation, and it doesn't affect interpreter execution (we
73-
// inspect the result after interpreter execution). Any undef byte is replaced with some
74-
// arbitrary byte value.
75-
//
76-
// FIXME: relay undef bytes to codegen as undef const bytes
77-
let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range);
78-
llvals.push(cx.const_bytes(bytes));
118+
// inspect the result after interpreter execution).
119+
append_chunks_of_init_and_uninit_bytes(&mut llvals, cx, alloc, range);
79120
}
80121

81122
cx.const_struct(&llvals, true)

compiler/rustc_middle/src/mir/interpret/allocation.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -721,20 +721,24 @@ impl InitMask {
721721
}
722722

723723
// FIXME(oli-obk): optimize this for allocations larger than a block.
724-
let idx = (start.bytes()..end.bytes()).map(Size::from_bytes).find(|&i| !self.get(i));
724+
let idx = (start..end).find(|&i| !self.get(i));
725725

726726
match idx {
727727
Some(idx) => {
728-
let uninit_end = (idx.bytes()..end.bytes())
729-
.map(Size::from_bytes)
730-
.find(|&i| self.get(i))
731-
.unwrap_or(end);
728+
let uninit_end = (idx..end).find(|&i| self.get(i)).unwrap_or(end);
732729
Err(idx..uninit_end)
733730
}
734731
None => Ok(()),
735732
}
736733
}
737734

735+
/// Returns an iterator, yielding a range of byte indexes for each contiguous region
736+
/// of initialized or uninitialized bytes inside the range `start..end` (end-exclusive).
737+
#[inline]
738+
pub fn range_as_init_chunks(&self, start: Size, end: Size) -> InitChunkIter<'_> {
739+
InitChunkIter::new(self, start, end)
740+
}
741+
738742
pub fn set_range(&mut self, start: Size, end: Size, new_state: bool) {
739743
let len = self.len;
740744
if end > len {
@@ -827,6 +831,49 @@ impl InitMask {
827831
}
828832
}
829833

834+
/// Yields [`InitChunk`]s. See [`InitMask::range_as_init_chunks`].
835+
pub struct InitChunkIter<'a> {
836+
init_mask: &'a InitMask,
837+
/// The current byte index into `init_mask`.
838+
start: Size,
839+
/// The end byte index into `init_mask`.
840+
end: Size,
841+
}
842+
843+
/// A contiguous chunk of initialized or uninitialized memory.
844+
pub enum InitChunk {
845+
Init(Range<Size>),
846+
Uninit(Range<Size>),
847+
}
848+
849+
impl<'a> InitChunkIter<'a> {
850+
fn new(init_mask: &'a InitMask, start: Size, end: Size) -> Self {
851+
assert!(start <= end);
852+
assert!(end <= init_mask.len);
853+
Self { init_mask, start, end }
854+
}
855+
}
856+
857+
impl<'a> Iterator for InitChunkIter<'a> {
858+
type Item = InitChunk;
859+
860+
fn next(&mut self) -> Option<Self::Item> {
861+
if self.start >= self.end {
862+
return None;
863+
}
864+
865+
let is_init = self.init_mask.get(self.start);
866+
// FIXME(oli-obk): optimize this for allocations larger than a block.
867+
let end_of_chunk =
868+
(self.start..self.end).find(|&i| self.init_mask.get(i) != is_init).unwrap_or(self.end);
869+
let range = self.start..end_of_chunk;
870+
871+
self.start = end_of_chunk;
872+
873+
Some(if is_init { InitChunk::Init(range) } else { InitChunk::Uninit(range) })
874+
}
875+
}
876+
830877
#[inline]
831878
fn bit_index(bits: Size) -> (usize, usize) {
832879
let bits = bits.bytes();

compiler/rustc_middle/src/mir/interpret/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ pub use self::error::{
125125

126126
pub use self::value::{get_slice_bytes, ConstAlloc, ConstValue, Scalar, ScalarMaybeUninit};
127127

128-
pub use self::allocation::{alloc_range, AllocRange, Allocation, InitMask, Relocations};
128+
pub use self::allocation::{
129+
alloc_range, AllocRange, Allocation, InitChunk, InitChunkIter, InitMask, Relocations,
130+
};
129131

130132
pub use self::pointer::{Pointer, PointerArithmetic, Provenance};
131133

compiler/rustc_target/src/abi/mod.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::spec::Target;
55

66
use std::convert::{TryFrom, TryInto};
77
use std::fmt;
8+
use std::iter::Step;
89
use std::num::NonZeroUsize;
910
use std::ops::{Add, AddAssign, Deref, Mul, Range, RangeInclusive, Sub};
1011
use std::str::FromStr;
@@ -440,6 +441,43 @@ impl AddAssign for Size {
440441
}
441442
}
442443

444+
impl Step for Size {
445+
#[inline]
446+
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
447+
u64::steps_between(&start.bytes(), &end.bytes())
448+
}
449+
450+
#[inline]
451+
fn forward_checked(start: Self, count: usize) -> Option<Self> {
452+
u64::forward_checked(start.bytes(), count).map(Self::from_bytes)
453+
}
454+
455+
#[inline]
456+
fn forward(start: Self, count: usize) -> Self {
457+
Self::from_bytes(u64::forward(start.bytes(), count))
458+
}
459+
460+
#[inline]
461+
unsafe fn forward_unchecked(start: Self, count: usize) -> Self {
462+
Self::from_bytes(u64::forward_unchecked(start.bytes(), count))
463+
}
464+
465+
#[inline]
466+
fn backward_checked(start: Self, count: usize) -> Option<Self> {
467+
u64::backward_checked(start.bytes(), count).map(Self::from_bytes)
468+
}
469+
470+
#[inline]
471+
fn backward(start: Self, count: usize) -> Self {
472+
Self::from_bytes(u64::backward(start.bytes(), count))
473+
}
474+
475+
#[inline]
476+
unsafe fn backward_unchecked(start: Self, count: usize) -> Self {
477+
Self::from_bytes(u64::backward_unchecked(start.bytes(), count))
478+
}
479+
}
480+
443481
/// Alignment of a type in bytes (always a power of two).
444482
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encodable, Decodable)]
445483
#[derive(HashStable_Generic)]

compiler/rustc_target/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#![feature(associated_type_bounds)]
1515
#![feature(exhaustive_patterns)]
1616
#![feature(min_specialization)]
17+
#![feature(step_trait)]
18+
#![feature(unchecked_math)]
1719

1820
use std::path::{Path, PathBuf};
1921

src/test/codegen/consts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ pub fn inline_enum_const() -> E<i8, i16> {
4343
#[no_mangle]
4444
pub fn low_align_const() -> E<i16, [i16; 3]> {
4545
// Check that low_align_const and high_align_const use the same constant
46-
// CHECK: memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 2 %1, i8* align 2 getelementptr inbounds (<{ [8 x i8] }>, <{ [8 x i8] }>* [[LOW_HIGH]], i32 0, i32 0, i32 0), i{{(32|64)}} 8, i1 false)
46+
// CHECK: memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 2 %1, i8* align 2 getelementptr inbounds (<{ [4 x i8], [4 x i8] }>, <{ [4 x i8], [4 x i8] }>* [[LOW_HIGH]], i32 0, i32 0, i32 0), i{{(32|64)}} 8, i1 false)
4747
*&E::A(0)
4848
}
4949

5050
// CHECK-LABEL: @high_align_const
5151
#[no_mangle]
5252
pub fn high_align_const() -> E<i16, i32> {
5353
// Check that low_align_const and high_align_const use the same constant
54-
// CHECK: memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 4 %1, i8* align 4 getelementptr inbounds (<{ [8 x i8] }>, <{ [8 x i8] }>* [[LOW_HIGH]], i32 0, i32 0, i32 0), i{{(32|64)}} 8, i1 false)
54+
// CHECK: memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 4 %1, i8* align 4 getelementptr inbounds (<{ [4 x i8], [4 x i8] }>, <{ [4 x i8], [4 x i8] }>* [[LOW_HIGH]], i32 0, i32 0, i32 0), i{{(32|64)}} 8, i1 false)
5555
*&E::A(0)
5656
}

src/test/codegen/uninit-consts.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// compile-flags: -C no-prepopulate-passes
2+
3+
// Check that we use undef (and not zero) for uninitialized bytes in constants.
4+
5+
#![crate_type = "lib"]
6+
7+
use std::mem::MaybeUninit;
8+
9+
pub struct PartiallyUninit {
10+
x: u32,
11+
y: MaybeUninit<[u8; 10]>
12+
}
13+
14+
// CHECK: [[FULLY_UNINIT:@[0-9]+]] = private unnamed_addr constant <{ [10 x i8] }> undef
15+
// CHECK: [[PARTIALLY_UNINIT:@[0-9]+]] = private unnamed_addr constant <{ [4 x i8], [12 x i8] }> <{ [4 x i8] c"\EF\BE\AD\DE", [12 x i8] undef }>, align 4
16+
// CHECK: [[FULLY_UNINIT_HUGE:@[0-9]+]] = private unnamed_addr constant <{ [16384 x i8] }> undef
17+
18+
// This shouldn't contain undef, since generating huge partially undef constants is expensive.
19+
// CHECK: [[UNINIT_PADDING_HUGE:@[0-9]+]] = private unnamed_addr constant <{ [32768 x i8] }> <{ [32768 x i8] c"{{.+}}" }>, align 4
20+
21+
// CHECK-LABEL: @fully_uninit
22+
#[no_mangle]
23+
pub const fn fully_uninit() -> MaybeUninit<[u8; 10]> {
24+
const M: MaybeUninit<[u8; 10]> = MaybeUninit::uninit();
25+
// CHECK: call void @llvm.memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 1 %1, i8* align 1 getelementptr inbounds (<{ [10 x i8] }>, <{ [10 x i8] }>* [[FULLY_UNINIT]], i32 0, i32 0, i32 0), i{{(32|64)}} 10, i1 false)
26+
M
27+
}
28+
29+
// CHECK-LABEL: @partially_uninit
30+
#[no_mangle]
31+
pub const fn partially_uninit() -> PartiallyUninit {
32+
const X: PartiallyUninit = PartiallyUninit { x: 0xdeadbeef, y: MaybeUninit::uninit() };
33+
// CHECK: call void @llvm.memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 4 %1, i8* align 4 getelementptr inbounds (<{ [4 x i8], [12 x i8] }>, <{ [4 x i8], [12 x i8] }>* [[PARTIALLY_UNINIT]], i32 0, i32 0, i32 0), i{{(32|64)}} 16, i1 false)
34+
X
35+
}
36+
37+
// CHECK-LABEL: @fully_uninit_huge
38+
#[no_mangle]
39+
pub const fn fully_uninit_huge() -> MaybeUninit<[u32; 4096]> {
40+
const F: MaybeUninit<[u32; 4096]> = MaybeUninit::uninit();
41+
// CHECK: call void @llvm.memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 4 %1, i8* align 4 getelementptr inbounds (<{ [16384 x i8] }>, <{ [16384 x i8] }>* [[FULLY_UNINIT_HUGE]], i32 0, i32 0, i32 0), i{{(32|64)}} 16384, i1 false)
42+
F
43+
}
44+
45+
// CHECK-LABEL: @uninit_padding_huge
46+
#[no_mangle]
47+
pub const fn uninit_padding_huge() -> [(u32, u8); 4096] {
48+
const X: [(u32, u8); 4096] = [(123, 45); 4096];
49+
// CHECK: call void @llvm.memcpy.p0i8.p0i8.i{{(32|64)}}(i8* align 4 %1, i8* align 4 getelementptr inbounds (<{ [32768 x i8] }>, <{ [32768 x i8] }>* [[UNINIT_PADDING_HUGE]], i32 0, i32 0, i32 0), i{{(32|64)}} 32768, i1 false)
50+
X
51+
}

0 commit comments

Comments
 (0)