1
Fork 0

Auto merge of #138634 - saethlin:repeated-uninit, r=scottmcm,oli-obk

Lower to a memset(undef) when Rvalue::Repeat repeats uninit

Fixes https://github.com/rust-lang/rust/issues/138625.

It is technically correct to just do nothing. But if we actually do nothing, we may miss that this is de-initializing something, so instead we just lower to a single memset that writes undef. This is still superior to the memcpy loop, in both quality of code we hand to the backend and LLVM's final output.
This commit is contained in:
bors 2025-03-25 02:09:15 +00:00
commit e61403aa4c
3 changed files with 66 additions and 4 deletions

View file

@ -86,13 +86,30 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
mir::Rvalue::Repeat(ref elem, count) => {
let cg_elem = self.codegen_operand(bx, elem);
// Do not generate the loop for zero-sized elements or empty arrays.
if dest.layout.is_zst() {
return;
}
// When the element is a const with all bytes uninit, emit a single memset that
// writes undef to the entire destination.
if let mir::Operand::Constant(const_op) = elem {
let val = self.eval_mir_constant(const_op);
if val.all_bytes_uninit(self.cx.tcx()) {
let size = bx.const_usize(dest.layout.size.bytes());
bx.memset(
dest.val.llval,
bx.const_undef(bx.type_i8()),
size,
dest.val.align,
MemFlags::empty(),
);
return;
}
}
let cg_elem = self.codegen_operand(bx, elem);
let try_init_all_same = |bx: &mut Bx, v| {
let start = dest.val.llval;
let size = bx.const_usize(dest.layout.size.bytes());

View file

@ -9,7 +9,9 @@ use rustc_span::{DUMMY_SP, Span, Symbol};
use rustc_type_ir::TypeVisitableExt;
use super::interpret::ReportedErrorInfo;
use crate::mir::interpret::{AllocId, ConstAllocation, ErrorHandled, Scalar, alloc_range};
use crate::mir::interpret::{
AllocId, AllocRange, ConstAllocation, ErrorHandled, GlobalAlloc, Scalar, alloc_range,
};
use crate::mir::{Promoted, pretty_print_const_value};
use crate::ty::print::{pretty_print_const, with_no_trimmed_paths};
use crate::ty::{self, ConstKind, GenericArgsRef, ScalarInt, Ty, TyCtxt};
@ -192,9 +194,31 @@ impl<'tcx> ConstValue<'tcx> {
.unwrap_memory()
.inner()
.provenance()
.range_empty(super::AllocRange::from(offset..offset + size), &tcx),
.range_empty(AllocRange::from(offset..offset + size), &tcx),
}
}
/// Check if a constant only contains uninitialized bytes.
pub fn all_bytes_uninit(&self, tcx: TyCtxt<'tcx>) -> bool {
let ConstValue::Indirect { alloc_id, .. } = self else {
return false;
};
let alloc = tcx.global_alloc(*alloc_id);
let GlobalAlloc::Memory(alloc) = alloc else {
return false;
};
let init_mask = alloc.0.init_mask();
let init_range = init_mask.is_range_initialized(AllocRange {
start: Size::ZERO,
size: Size::from_bytes(alloc.0.len()),
});
if let Err(range) = init_range {
if range.size == alloc.0.size() {
return true;
}
}
false
}
}
///////////////////////////////////////////////////////////////////////////

View file

@ -0,0 +1,21 @@
//@ compile-flags: -Copt-level=3
#![crate_type = "lib"]
use std::mem::MaybeUninit;
// We need to make sure len is at offset 0, otherwise codegen needs an extra instruction
#[repr(C)]
pub struct SmallVec<T> {
pub len: u64,
pub arr: [MaybeUninit<T>; 24],
}
// CHECK-LABEL: @uninit_arr_via_const
#[no_mangle]
pub fn uninit_arr_via_const() -> SmallVec<String> {
// CHECK-NEXT: start:
// CHECK-NEXT: store i64 0,
// CHECK-NEXT: ret
SmallVec { len: 0, arr: [const { MaybeUninit::uninit() }; 24] }
}