Skip to content

Commit f751a56

Browse files
committed
Auto merge of #152786 - scottmcm:layout_of_val, r=<try>
Add a `layout_of_val` intrinsic for when you want both `size_of_val`+`align_of_val`
2 parents 8387095 + ddf6fe3 commit f751a56

15 files changed

Lines changed: 266 additions & 204 deletions

compiler/rustc_codegen_llvm/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,12 @@ impl CodegenBackend for LlvmCodegenBackend {
354354
}
355355

356356
fn replaced_intrinsics(&self) -> Vec<Symbol> {
357-
let mut will_not_use_fallback =
358-
vec![sym::unchecked_funnel_shl, sym::unchecked_funnel_shr, sym::carrying_mul_add];
357+
let mut will_not_use_fallback = vec![
358+
sym::unchecked_funnel_shl,
359+
sym::unchecked_funnel_shr,
360+
sym::carrying_mul_add,
361+
sym::layout_of_val,
362+
];
359363

360364
if llvm_util::get_version() >= (22, 0, 0) {
361365
will_not_use_fallback.push(sym::carryless_mul);

compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_abi::WrappingRange;
1+
use rustc_abi::{FieldIdx, WrappingRange};
22
use rustc_middle::mir::SourceInfo;
33
use rustc_middle::ty::{self, Ty, TyCtxt};
44
use rustc_middle::{bug, span_bug};
@@ -7,7 +7,7 @@ use rustc_span::sym;
77
use rustc_target::spec::Arch;
88

99
use super::FunctionCx;
10-
use super::operand::OperandRef;
10+
use super::operand::{OperandRef, OperandRefBuilder};
1111
use super::place::PlaceRef;
1212
use crate::common::{AtomicRmwBinOp, SynchronizationScope};
1313
use crate::errors::InvalidMonomorphization;
@@ -149,17 +149,26 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
149149

150150
sym::va_start => bx.va_start(args[0].immediate()),
151151
sym::va_end => bx.va_end(args[0].immediate()),
152-
sym::size_of_val => {
152+
sym::size_of_val | sym::align_of_val | sym::layout_of_val => {
153153
let tp_ty = fn_args.type_at(0);
154154
let (_, meta) = args[0].val.pointer_parts();
155-
let (llsize, _) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
156-
llsize
157-
}
158-
sym::align_of_val => {
159-
let tp_ty = fn_args.type_at(0);
160-
let (_, meta) = args[0].val.pointer_parts();
161-
let (_, llalign) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
162-
llalign
155+
let (llsize, llalign) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
156+
match name {
157+
sym::size_of_val => llsize,
158+
sym::align_of_val => llalign,
159+
sym::layout_of_val => {
160+
// Use the builder so we're insulated from the in-memory field order
161+
let mut builder = OperandRefBuilder::<'_, Bx::Value>::new(result.layout);
162+
builder.insert_imm(FieldIdx::from_u32(0), llsize);
163+
builder.insert_imm(FieldIdx::from_u32(1), llalign);
164+
let val = builder.build(bx.cx()).val;
165+
// the match can only return a single `Bx::Value`,
166+
// so we need to do the store and return.
167+
val.store(bx, result);
168+
return Ok(());
169+
}
170+
_ => bug!(),
171+
}
163172
}
164173
sym::vtable_size | sym::vtable_align => {
165174
let vtable = args[0].immediate();
@@ -179,9 +188,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
179188
let size_bound = bx.data_layout().ptr_sized_integer().signed_max() as u128;
180189
bx.range_metadata(value, WrappingRange { start: 0, end: size_bound });
181190
}
182-
// Alignment is always nonzero.
191+
// Alignment is always a power of two, thus 1..=0x800…000.
183192
sym::vtable_align => {
184-
bx.range_metadata(value, WrappingRange { start: 1, end: !0 })
193+
let align_bound = bx.data_layout().ptr_sized_integer().signed_min() as u128;
194+
bx.range_metadata(value, WrappingRange { start: 1, end: align_bound })
185195
}
186196
_ => {}
187197
}

compiler/rustc_codegen_ssa/src/size_of_val.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
3636
// Size is always <= isize::MAX.
3737
let size_bound = bx.data_layout().ptr_sized_integer().signed_max() as u128;
3838
bx.range_metadata(size, WrappingRange { start: 0, end: size_bound });
39-
// Alignment is always nonzero.
40-
bx.range_metadata(align, WrappingRange { start: 1, end: !0 });
39+
// Alignment is always a power of two, thus 1..=0x800…000.
40+
let align_bound = size_bound + 1;
41+
bx.range_metadata(align, WrappingRange { start: 1, end: align_bound });
4142

4243
(size, align)
4344
}
@@ -157,7 +158,12 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
157158
// Furthermore, `align >= unsized_align`, and therefore we only need to do:
158159
// let full_size = (unsized_offset_unadjusted + unsized_size).align_to(full_align);
159160

160-
let full_size = bx.add(unsized_offset_unadjusted, unsized_size);
161+
// This is the size *before* rounding up, which cannot exceed the size *after*
162+
// rounding up, which itself cannot exceed `isize::MAX`. Thus the addition
163+
// itself cannot overflow `isize::MAX`, let alone `usize::MAX`.
164+
// (The range attribute from loading the size from the vtable is enough to prove
165+
// `nuw`, but not `nsw`, which we only know from Rust's layout rules.)
166+
let full_size = bx.unchecked_suadd(unsized_offset_unadjusted, unsized_size);
161167

162168
// Issue #27023: must add any necessary padding to `size`
163169
// (to make it a multiple of `align`) before returning it.

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ pub(crate) fn check_intrinsic_type(
296296
sym::size_of_val | sym::align_of_val => {
297297
(1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.usize)
298298
}
299+
sym::layout_of_val => {
300+
(1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.ty_alloc_layout(span))
301+
}
299302
sym::offset_of => (1, 0, vec![tcx.types.u32, tcx.types.u32], tcx.types.usize),
300303
sym::rustc_peek => (1, 0, vec![param(0)], param(0)),
301304
sym::caller_location => (0, 0, vec![], tcx.caller_location_ty()),

compiler/rustc_middle/src/ty/context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,12 @@ impl<'tcx> TyCtxt<'tcx> {
10791079
self.type_of(ordering_enum).no_bound_vars().unwrap()
10801080
}
10811081

1082+
/// Gets a `Ty` representing the [`LangItem::Alignment`]
1083+
pub fn ty_alloc_layout(self, span: Span) -> Ty<'tcx> {
1084+
let layout_did = self.require_lang_item(hir::LangItem::AllocLayout, span);
1085+
self.type_of(layout_did).no_bound_vars().unwrap()
1086+
}
1087+
10821088
/// Obtain the given diagnostic item's `DefId`. Use `is_diagnostic_item` if you just want to
10831089
/// compare against another `DefId`, since `is_diagnostic_item` is cheaper.
10841090
pub fn get_diagnostic_item(self, name: Symbol) -> Option<DefId> {

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,7 @@ symbols! {
13481348
large_assignments,
13491349
last,
13501350
lateout,
1351+
layout_of_val,
13511352
lazy_normalization_consts,
13521353
lazy_type_alias,
13531354
le,

library/core/src/alloc/layout.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Your performance intuition is useless. Run perf.
66

77
use crate::error::Error;
8-
use crate::intrinsics::{unchecked_add, unchecked_mul, unchecked_sub};
8+
use crate::intrinsics::{self, unchecked_add, unchecked_mul, unchecked_sub};
99
use crate::mem::SizedTypeProperties;
1010
use crate::ptr::{Alignment, NonNull};
1111
use crate::{assert_unsafe_precondition, fmt, mem};
@@ -250,11 +250,9 @@ impl Layout {
250250
#[unstable(feature = "layout_for_ptr", issue = "69835")]
251251
#[must_use]
252252
#[inline]
253-
pub const unsafe fn for_value_raw<T: ?Sized>(t: *const T) -> Self {
253+
pub const unsafe fn for_value_raw<T: ?Sized>(ptr: *const T) -> Self {
254254
// SAFETY: we pass along the prerequisites of these functions to the caller
255-
let (size, alignment) = unsafe { (mem::size_of_val_raw(t), Alignment::of_val_raw(t)) };
256-
// SAFETY: see rationale in `new` for why this is using the unsafe variant
257-
unsafe { Layout::from_size_alignment_unchecked(size, alignment) }
255+
unsafe { intrinsics::layout_of_val(ptr) }
258256
}
259257

260258
/// Creates a `NonNull` that is dangling, but well-aligned for this Layout.

library/core/src/intrinsics/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
issue = "none"
5454
)]
5555

56+
use crate::alloc::Layout;
5657
use crate::ffi::va_list::{VaArgSafe, VaList};
5758
use crate::marker::{ConstParamTy, DiscriminantKind, PointeeSized, Tuple};
5859
use crate::{mem, ptr};
@@ -2864,6 +2865,26 @@ pub const unsafe fn size_of_val<T: ?Sized>(ptr: *const T) -> usize;
28642865
#[rustc_intrinsic_const_stable_indirect]
28652866
pub const unsafe fn align_of_val<T: ?Sized>(ptr: *const T) -> usize;
28662867

2868+
/// The size and alignment of the referenced value in bytes.
2869+
///
2870+
/// The stabilized version of this intrinsic is [`Layout::for_value_raw`].
2871+
///
2872+
/// # Safety
2873+
///
2874+
/// See [`Layout::for_value_raw`] for safety conditions.
2875+
#[rustc_nounwind]
2876+
#[unstable(feature = "core_intrinsics", issue = "none")]
2877+
#[rustc_intrinsic]
2878+
// This adds no semantics or UB atop just calling `size_of_val`+`align_of_val`.
2879+
#[miri::intrinsic_fallback_is_spec]
2880+
pub const unsafe fn layout_of_val<T: ?Sized>(ptr: *const T) -> Layout {
2881+
// SAFETY: we pass along the prerequisites of these functions to the caller
2882+
let (size, align) = unsafe { (size_of_val(ptr), align_of_val(ptr)) };
2883+
// SAFETY: The size and alignment of a valid allocation (or type)
2884+
// always meet the requirements of `Layout`.
2885+
unsafe { Layout::from_size_align_unchecked(size, align) }
2886+
}
2887+
28672888
/// Compute the type information of a concrete type.
28682889
/// It can only be called at compile time, the backends do
28692890
/// not implement it.

tests/codegen-llvm/dst-vtable-align-nonzero.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,4 @@ pub unsafe fn align_load_from_vtable_align_intrinsic(x: &dyn Trait) -> usize {
6464
core::intrinsics::vtable_align(vtable)
6565
}
6666

67-
// CHECK: [[RANGE_META]] = !{[[USIZE]] 1, [[USIZE]] 0}
67+
// CHECK: [[RANGE_META]] = !{[[USIZE]] 1, [[USIZE]] {{-2147483647|-9223372036854775807}}}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes -Z inline-mir
2+
//@ only-64bit (so I don't need to worry about usize)
3+
//@ needs-deterministic-layouts
4+
5+
// Note that the layout algorithm currently puts the align before the size,
6+
// because the *type* for the size doesn't have a niche. This test may need
7+
// to be updated if the in-memory field order of `Layout` ever changes.
8+
9+
#![crate_type = "lib"]
10+
#![feature(core_intrinsics)]
11+
12+
use std::alloc::Layout;
13+
use std::intrinsics::layout_of_val;
14+
15+
// CHECK-LABEL: @thin_metadata(
16+
#[no_mangle]
17+
pub unsafe fn thin_metadata(ptr: *const [u32; 2]) -> Layout {
18+
// CHECK: [[LAYOUT:%.+]] = alloca [16 x i8], align 8
19+
// CHECK-NOT: load
20+
// CHECK-NOT: store
21+
// CHECK: store i64 4, ptr [[LAYOUT]], align 8
22+
// CHECK-NEXT: [[SIZEP:%.+]] = getelementptr inbounds i8, ptr [[LAYOUT]], i64 8
23+
// CHECK-NEXT: store i64 8, ptr [[SIZEP]], align 8
24+
// CHECK-NOT: store
25+
layout_of_val(ptr)
26+
}
27+
28+
// CHECK-LABEL: @slice_metadata(ptr noundef %ptr.0, i64 noundef %ptr.1)
29+
#[no_mangle]
30+
pub unsafe fn slice_metadata(ptr: *const [u32]) -> Layout {
31+
// CHECK: [[LAYOUT:%.+]] = alloca [16 x i8], align 8
32+
// CHECK-NOT: load
33+
// CHECK-NOT: store
34+
// CHECK: [[BYTES:%.+]] = mul nuw nsw i64 %ptr.1, 4
35+
// CHECK-NEXT: store i64 4, ptr [[LAYOUT]], align 8
36+
// CHECK-NEXT: [[SIZEP:%.+]] = getelementptr inbounds i8, ptr [[LAYOUT]], i64 8
37+
// CHECK-NEXT: store i64 [[BYTES]], ptr [[SIZEP]], align 8
38+
// CHECK-NOT: store
39+
layout_of_val(ptr)
40+
}
41+
42+
pub struct WithTail<T: ?Sized>([u32; 3], T);
43+
44+
// CHECK-LABEL: @dst_metadata
45+
// CHECK-SAME: (ptr noundef %ptr.0, ptr{{.+}}%ptr.1)
46+
#[no_mangle]
47+
pub unsafe fn dst_metadata(ptr: *const WithTail<dyn std::fmt::Debug>) -> Layout {
48+
// CHECK: [[LAYOUT:%.+]] = alloca [16 x i8], align 8
49+
// CHECK-NOT: load
50+
// CHECK-NOT: store
51+
// CHECK: [[DST_SIZEP:%.+]] = getelementptr inbounds i8, ptr %ptr.1, i64 8
52+
// CHECK-NEXT: [[DST_SIZE:%.+]] = load i64, ptr [[DST_SIZEP]], align 8,
53+
// CHECK-SAME: !range [[SIZE_RANGE:.+]], !invariant.load
54+
// CHECK-NEXT: [[DST_ALIGNP:%.+]] = getelementptr inbounds i8, ptr %ptr.1, i64 16
55+
// CHECK-NEXT: [[DST_ALIGN:%.+]] = load i64, ptr [[DST_ALIGNP]], align 8,
56+
// CHECK-SAME: !range [[ALIGN_RANGE:!.+]], !invariant.load
57+
58+
// CHECK-NEXT: [[STRUCT_MORE:%.+]] = icmp ugt i64 4, [[DST_ALIGN]]
59+
// CHECK-NEXT: [[ALIGN:%.+]] = select i1 [[STRUCT_MORE]], i64 4, i64 [[DST_ALIGN]]
60+
61+
// CHECK-NEXT: [[MINSIZE:%.+]] = add nuw nsw i64 12, [[DST_SIZE]]
62+
// CHECK-NEXT: [[ALIGN_M1:%.+]] = sub i64 [[ALIGN]], 1
63+
// CHECK-NEXT: [[MAXSIZE:%.+]] = add i64 [[MINSIZE]], [[ALIGN_M1]]
64+
// CHECK-NEXT: [[ALIGN_NEG:%.+]] = sub i64 0, [[ALIGN]]
65+
// CHECK-NEXT: [[SIZE:%.+]] = and i64 [[MAXSIZE]], [[ALIGN_NEG]]
66+
67+
// CHECK-NEXT: store i64 [[ALIGN]], ptr [[LAYOUT]], align 8
68+
// CHECK-NEXT: [[LAYOUT_SIZEP:%.+]] = getelementptr inbounds i8, ptr [[LAYOUT]], i64 8
69+
// CHECK-NEXT: store i64 [[SIZE]], ptr [[LAYOUT_SIZEP]], align 8
70+
// CHECK-NOT: store
71+
layout_of_val(ptr)
72+
}
73+
74+
// CHECK-LABEL: declare
75+
76+
// CHECK: [[ALIGN_RANGE]] = !{i64 1, i64 -[[#0x7FFFFFFFFFFFFFFF]]
77+
// CHECK: [[SIZE_RANGE]] = !{i64 0, i64 -[[#0x8000000000000000]]

0 commit comments

Comments
 (0)