Skip to content

Commit 38104f3

Browse files
committed
Auto merge of rust-lang#123936 - Mark-Simulacrum:zst-no-alloc, r=oli-obk
Codegen ZSTs without an allocation This makes sure that &[] is equivalent to unsafe code (from_raw_parts(dangling, 0)). No new stable guarantee is intended about whether or not we do this, this is just an optimization. This regressed in rust-lang#67000 (no comments I can see about that regression in the PR, though it did change the test modified here). We had previously performed this optimization since rust-lang#63635.
2 parents c45dee5 + 9ab6e36 commit 38104f3

File tree

4 files changed

+63
-27
lines changed

4 files changed

+63
-27
lines changed

compiler/rustc_codegen_llvm/src/common.rs

+33-16
Original file line numberDiff line numberDiff line change
@@ -255,21 +255,38 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
255255
let (prov, offset) = ptr.into_parts();
256256
let (base_addr, base_addr_space) = match self.tcx.global_alloc(prov.alloc_id()) {
257257
GlobalAlloc::Memory(alloc) => {
258-
let init = const_alloc_to_llvm(self, alloc);
259-
let alloc = alloc.inner();
260-
let value = match alloc.mutability {
261-
Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None),
262-
_ => self.static_addr_of(init, alloc.align, None),
263-
};
264-
if !self.sess().fewer_names() && llvm::get_value_name(value).is_empty() {
265-
let hash = self.tcx.with_stable_hashing_context(|mut hcx| {
266-
let mut hasher = StableHasher::new();
267-
alloc.hash_stable(&mut hcx, &mut hasher);
268-
hasher.finish::<Hash128>()
269-
});
270-
llvm::set_value_name(value, format!("alloc_{hash:032x}").as_bytes());
258+
// For ZSTs directly codegen an aligned pointer.
259+
// This avoids generating a zero-sized constant value and actually needing a
260+
// real address at runtime.
261+
if alloc.inner().len() == 0 {
262+
assert_eq!(offset.bytes(), 0);
263+
let llval = self.const_usize(alloc.inner().align.bytes());
264+
return if matches!(layout.primitive(), Pointer(_)) {
265+
unsafe { llvm::LLVMConstIntToPtr(llval, llty) }
266+
} else {
267+
self.const_bitcast(llval, llty)
268+
};
269+
} else {
270+
let init = const_alloc_to_llvm(self, alloc, /*static*/ false);
271+
let alloc = alloc.inner();
272+
let value = match alloc.mutability {
273+
Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None),
274+
_ => self.static_addr_of(init, alloc.align, None),
275+
};
276+
if !self.sess().fewer_names() && llvm::get_value_name(value).is_empty()
277+
{
278+
let hash = self.tcx.with_stable_hashing_context(|mut hcx| {
279+
let mut hasher = StableHasher::new();
280+
alloc.hash_stable(&mut hcx, &mut hasher);
281+
hasher.finish::<Hash128>()
282+
});
283+
llvm::set_value_name(
284+
value,
285+
format!("alloc_{hash:032x}").as_bytes(),
286+
);
287+
}
288+
(value, AddressSpace::DATA)
271289
}
272-
(value, AddressSpace::DATA)
273290
}
274291
GlobalAlloc::Function(fn_instance) => (
275292
self.get_fn_addr(fn_instance.polymorphize(self.tcx)),
@@ -280,7 +297,7 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
280297
.tcx
281298
.global_alloc(self.tcx.vtable_allocation((ty, trait_ref)))
282299
.unwrap_memory();
283-
let init = const_alloc_to_llvm(self, alloc);
300+
let init = const_alloc_to_llvm(self, alloc, /*static*/ false);
284301
let value = self.static_addr_of(init, alloc.inner().align, None);
285302
(value, AddressSpace::DATA)
286303
}
@@ -308,7 +325,7 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
308325
}
309326

310327
fn const_data_from_alloc(&self, alloc: ConstAllocation<'tcx>) -> Self::Value {
311-
const_alloc_to_llvm(self, alloc)
328+
const_alloc_to_llvm(self, alloc, /*static*/ false)
312329
}
313330

314331
fn const_bitcast(&self, val: &'ll Value, ty: &'ll Type) -> &'ll Value {

compiler/rustc_codegen_llvm/src/consts.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,22 @@ use rustc_target::abi::{
2626
};
2727
use std::ops::Range;
2828

29-
pub fn const_alloc_to_llvm<'ll>(cx: &CodegenCx<'ll, '_>, alloc: ConstAllocation<'_>) -> &'ll Value {
29+
pub fn const_alloc_to_llvm<'ll>(
30+
cx: &CodegenCx<'ll, '_>,
31+
alloc: ConstAllocation<'_>,
32+
is_static: bool,
33+
) -> &'ll Value {
3034
let alloc = alloc.inner();
35+
// We expect that callers of const_alloc_to_llvm will instead directly codegen a pointer or
36+
// integer for any &ZST where the ZST is a constant (i.e. not a static). We should never be
37+
// producing empty LLVM allocations as they're just adding noise to binaries and forcing less
38+
// optimal codegen.
39+
//
40+
// Statics have a guaranteed meaningful address so it's less clear that we want to do
41+
// something like this; it's also harder.
42+
if !is_static {
43+
assert!(alloc.len() != 0);
44+
}
3145
let mut llvals = Vec::with_capacity(alloc.provenance().ptrs().len() + 1);
3246
let dl = cx.data_layout();
3347
let pointer_size = dl.pointer_size.bytes() as usize;
@@ -120,7 +134,7 @@ fn codegen_static_initializer<'ll, 'tcx>(
120134
def_id: DefId,
121135
) -> Result<(&'ll Value, ConstAllocation<'tcx>), ErrorHandled> {
122136
let alloc = cx.tcx.eval_static_initializer(def_id)?;
123-
Ok((const_alloc_to_llvm(cx, alloc), alloc))
137+
Ok((const_alloc_to_llvm(cx, alloc, /*static*/ true), alloc))
124138
}
125139

126140
fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) {

tests/run-make/static-pie/test-aslr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn main() {
1717
let arg0 = args.next().unwrap();
1818
match args.next() {
1919
Some(s) if s.eq("--report") => {
20-
println!("main = {:#?}", &main as *const _);
20+
println!("main = {:#?}", main as fn() as usize);
2121
}
2222
Some(s) if s.eq("--test-no-aslr") => {
2323
let cnt = run_self(&arg0);

tests/ui/consts/zst_no_llvm_alloc.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ struct Foo;
66
static FOO: Foo = Foo;
77

88
fn main() {
9+
// There's no stable guarantee that these are true.
10+
// However, we want them to be true so that our LLVM IR and runtime are a bit faster:
11+
// a constant address is cheap and doesn't result in relocations in comparison to a "real"
12+
// global somewhere in the data section.
913
let x: &'static () = &();
10-
assert_ne!(x as *const () as usize, 1);
14+
assert_eq!(x as *const () as usize, 1);
1115
let x: &'static Foo = &Foo;
12-
assert_ne!(x as *const Foo as usize, 4);
16+
assert_eq!(x as *const Foo as usize, 4);
1317

14-
// statics must have a unique address
15-
assert_ne!(&FOO as *const Foo as usize, 4);
18+
// The exact addresses returned by these library functions are not necessarily stable guarantees
19+
// but for now we assert that we're still matching.
20+
assert_eq!(<Vec<i32>>::new().as_ptr(), <&[i32]>::default().as_ptr());
21+
assert_eq!(<Box<[i32]>>::default().as_ptr(), (&[]).as_ptr());
1622

17-
// FIXME this two tests should be assert_eq!
18-
// this stopped working since we are promoting to constants instead of statics
19-
assert_ne!(<Vec<i32>>::new().as_ptr(), <&[i32]>::default().as_ptr());
20-
assert_ne!(<Box<[i32]>>::default().as_ptr(), (&[]).as_ptr());
23+
// statics must have a unique address (see https://github.com/rust-lang/rust/issues/18297, not
24+
// clear whether this is a stable guarantee)
25+
assert_ne!(&FOO as *const Foo as usize, 4);
2126
}

0 commit comments

Comments
 (0)