Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions compiler/rustc_codegen_llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,12 @@ impl CodegenBackend for LlvmCodegenBackend {
}

fn replaced_intrinsics(&self) -> Vec<Symbol> {
let mut will_not_use_fallback =
vec![sym::unchecked_funnel_shl, sym::unchecked_funnel_shr, sym::carrying_mul_add];
let mut will_not_use_fallback = vec![
sym::unchecked_funnel_shl,
sym::unchecked_funnel_shr,
sym::carrying_mul_add,
sym::layout_of_val,
];

if llvm_util::get_version() >= (22, 0, 0) {
will_not_use_fallback.push(sym::carryless_mul);
Expand Down
36 changes: 23 additions & 13 deletions compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_abi::WrappingRange;
use rustc_abi::{FieldIdx, WrappingRange};
use rustc_middle::mir::SourceInfo;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
Expand All @@ -7,7 +7,7 @@ use rustc_span::sym;
use rustc_target::spec::Arch;

use super::FunctionCx;
use super::operand::OperandRef;
use super::operand::{OperandRef, OperandRefBuilder};
use super::place::PlaceRef;
use crate::common::{AtomicRmwBinOp, SynchronizationScope};
use crate::errors::InvalidMonomorphization;
Expand Down Expand Up @@ -149,17 +149,26 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {

sym::va_start => bx.va_start(args[0].immediate()),
sym::va_end => bx.va_end(args[0].immediate()),
sym::size_of_val => {
sym::size_of_val | sym::align_of_val | sym::layout_of_val => {
let tp_ty = fn_args.type_at(0);
let (_, meta) = args[0].val.pointer_parts();
let (llsize, _) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
llsize
}
sym::align_of_val => {
let tp_ty = fn_args.type_at(0);
let (_, meta) = args[0].val.pointer_parts();
let (_, llalign) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
llalign
let (llsize, llalign) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta);
match name {
sym::size_of_val => llsize,
sym::align_of_val => llalign,
sym::layout_of_val => {
// Use the builder so we're insulated from the in-memory field order
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're still relying on the in-source field order here. That deserves a comment on both ends (here and in the library). Or can the field lookup here be done by name?

let mut builder = OperandRefBuilder::<'_, Bx::Value>::new(result.layout);
builder.insert_imm(FieldIdx::from_u32(0), llsize);
builder.insert_imm(FieldIdx::from_u32(1), llalign);
let val = builder.build(bx.cx()).val;
// the match can only return a single `Bx::Value`,
// so we need to do the store and return.
val.store(bx, result);
return Ok(());
}
_ => bug!(),
}
}
sym::vtable_size | sym::vtable_align => {
let vtable = args[0].immediate();
Expand All @@ -179,9 +188,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let size_bound = bx.data_layout().ptr_sized_integer().signed_max() as u128;
bx.range_metadata(value, WrappingRange { start: 0, end: size_bound });
}
// Alignment is always nonzero.
// Alignment is always a power of two, thus 1..=0x800…000.
sym::vtable_align => {
bx.range_metadata(value, WrappingRange { start: 1, end: !0 })
let align_bound = bx.data_layout().ptr_sized_integer().signed_min() as u128;
bx.range_metadata(value, WrappingRange { start: 1, end: align_bound })
}
_ => {}
}
Expand Down
12 changes: 9 additions & 3 deletions compiler/rustc_codegen_ssa/src/size_of_val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ pub fn size_and_align_of_dst<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
// Size is always <= isize::MAX.
let size_bound = bx.data_layout().ptr_sized_integer().signed_max() as u128;
bx.range_metadata(size, WrappingRange { start: 0, end: size_bound });
// Alignment is always nonzero.
bx.range_metadata(align, WrappingRange { start: 1, end: !0 });
// Alignment is always a power of two, thus 1..=0x800…000.
let align_bound = size_bound + 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact the bound is Align::MAX, is it worth emitting that?

Copy link
Copy Markdown
Member Author

@scottmcm scottmcm Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, really good point 👍 I was stuck still thinking of it as a load of ptr::Alignment, but it's not.

I don't know how much it'll really matter, since isize::MAX+1 is enough to prove that size+align doesn't unsigned-overflow, but might as well include it because it's easy, cheap, clearly not worse, and makes the test verification check slightly nicer.

bx.range_metadata(align, WrappingRange { start: 1, end: align_bound });

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

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

// Issue #27023: must add any necessary padding to `size`
// (to make it a multiple of `align`) before returning it.
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ pub(crate) fn check_intrinsic_type(
sym::size_of_val | sym::align_of_val => {
(1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.usize)
}
sym::layout_of_val => {
(1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.ty_alloc_layout(span))
}
sym::offset_of => (1, 0, vec![tcx.types.u32, tcx.types.u32], tcx.types.usize),
sym::rustc_peek => (1, 0, vec![param(0)], param(0)),
sym::caller_location => (0, 0, vec![], tcx.caller_location_ty()),
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,12 @@ impl<'tcx> TyCtxt<'tcx> {
self.type_of(ordering_enum).no_bound_vars().unwrap()
}

/// Gets a `Ty` representing the [`LangItem::AllocLayout`]
pub fn ty_alloc_layout(self, span: Span) -> Ty<'tcx> {
let layout_did = self.require_lang_item(hir::LangItem::AllocLayout, span);
self.type_of(layout_did).no_bound_vars().unwrap()
}

/// Obtain the given diagnostic item's `DefId`. Use `is_diagnostic_item` if you just want to
/// compare against another `DefId`, since `is_diagnostic_item` is cheaper.
pub fn get_diagnostic_item(self, name: Symbol) -> Option<DefId> {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,7 @@ symbols! {
large_assignments,
last,
lateout,
layout_of_val,
lazy_normalization_consts,
lazy_type_alias,
le,
Expand Down
8 changes: 3 additions & 5 deletions library/core/src/alloc/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Your performance intuition is useless. Run perf.

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

/// Creates a `NonNull` that is dangling, but well-aligned for this Layout.
Expand Down
21 changes: 21 additions & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
issue = "none"
)]

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

/// The size and alignment of the referenced value in bytes.
///
/// The stabilized version of this intrinsic is [`Layout::for_value_raw`].
///
/// # Safety
///
/// See [`Layout::for_value_raw`] for safety conditions.
#[rustc_nounwind]
#[unstable(feature = "core_intrinsics", issue = "none")]
#[rustc_intrinsic]
// This adds no semantics or UB atop just calling `size_of_val`+`align_of_val`.
#[miri::intrinsic_fallback_is_spec]
pub const unsafe fn layout_of_val<T: ?Sized>(ptr: *const T) -> Layout {
// SAFETY: we pass along the prerequisites of these functions to the caller
let (size, align) = unsafe { (size_of_val(ptr), align_of_val(ptr)) };
// SAFETY: The size and alignment of a valid allocation (or type)
// always meet the requirements of `Layout`.
unsafe { Layout::from_size_align_unchecked(size, align) }
}

/// Compute the type information of a concrete type.
/// It can only be called at compile time, the backends do
/// not implement it.
Expand Down
2 changes: 1 addition & 1 deletion tests/codegen-llvm/dst-vtable-align-nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ pub unsafe fn align_load_from_vtable_align_intrinsic(x: &dyn Trait) -> usize {
core::intrinsics::vtable_align(vtable)
}

// CHECK: [[RANGE_META]] = !{[[USIZE]] 1, [[USIZE]] 0}
// CHECK: [[RANGE_META]] = !{[[USIZE]] 1, [[USIZE]] {{-2147483647|-9223372036854775807}}}
77 changes: 77 additions & 0 deletions tests/codegen-llvm/intrinsics/layout_of_val.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes -Z inline-mir
//@ only-64bit (so I don't need to worry about usize)
//@ needs-deterministic-layouts

// Note that the layout algorithm currently puts the align before the size,
// because the *type* for the size doesn't have a niche. This test may need
// to be updated if the in-memory field order of `Layout` ever changes.

#![crate_type = "lib"]
#![feature(core_intrinsics)]

use std::alloc::Layout;
use std::intrinsics::layout_of_val;

// CHECK-LABEL: @thin_metadata(
#[no_mangle]
pub unsafe fn thin_metadata(ptr: *const [u32; 2]) -> Layout {
// CHECK: [[LAYOUT:%.+]] = alloca [16 x i8], align 8
// CHECK-NOT: load
// CHECK-NOT: store
// CHECK: store i64 4, ptr [[LAYOUT]], align 8
// CHECK-NEXT: [[SIZEP:%.+]] = getelementptr inbounds i8, ptr [[LAYOUT]], i64 8
// CHECK-NEXT: store i64 8, ptr [[SIZEP]], align 8
// CHECK-NOT: store
layout_of_val(ptr)
}

// CHECK-LABEL: @slice_metadata(ptr noundef %ptr.0, i64 noundef %ptr.1)
#[no_mangle]
pub unsafe fn slice_metadata(ptr: *const [u32]) -> Layout {
// CHECK: [[LAYOUT:%.+]] = alloca [16 x i8], align 8
// CHECK-NOT: load
// CHECK-NOT: store
// CHECK: [[BYTES:%.+]] = mul nuw nsw i64 %ptr.1, 4
// CHECK-NEXT: store i64 4, ptr [[LAYOUT]], align 8
// CHECK-NEXT: [[SIZEP:%.+]] = getelementptr inbounds i8, ptr [[LAYOUT]], i64 8
// CHECK-NEXT: store i64 [[BYTES]], ptr [[SIZEP]], align 8
// CHECK-NOT: store
layout_of_val(ptr)
}

pub struct WithTail<T: ?Sized>([u32; 3], T);

// CHECK-LABEL: @dst_metadata
// CHECK-SAME: (ptr noundef %ptr.0, ptr{{.+}}%ptr.1)
#[no_mangle]
pub unsafe fn dst_metadata(ptr: *const WithTail<dyn std::fmt::Debug>) -> Layout {
// CHECK: [[LAYOUT:%.+]] = alloca [16 x i8], align 8
// CHECK-NOT: load
// CHECK-NOT: store
// CHECK: [[DST_SIZEP:%.+]] = getelementptr inbounds i8, ptr %ptr.1, i64 8
// CHECK-NEXT: [[DST_SIZE:%.+]] = load i64, ptr [[DST_SIZEP]], align 8,
// CHECK-SAME: !range [[SIZE_RANGE:.+]], !invariant.load
// CHECK-NEXT: [[DST_ALIGNP:%.+]] = getelementptr inbounds i8, ptr %ptr.1, i64 16
// CHECK-NEXT: [[DST_ALIGN:%.+]] = load i64, ptr [[DST_ALIGNP]], align 8,
// CHECK-SAME: !range [[ALIGN_RANGE:!.+]], !invariant.load

// CHECK-NEXT: [[STRUCT_MORE:%.+]] = icmp ugt i64 4, [[DST_ALIGN]]
// CHECK-NEXT: [[ALIGN:%.+]] = select i1 [[STRUCT_MORE]], i64 4, i64 [[DST_ALIGN]]

// CHECK-NEXT: [[MINSIZE:%.+]] = add nuw nsw i64 12, [[DST_SIZE]]
// CHECK-NEXT: [[ALIGN_M1:%.+]] = sub i64 [[ALIGN]], 1
// CHECK-NEXT: [[MAXSIZE:%.+]] = add i64 [[MINSIZE]], [[ALIGN_M1]]
// CHECK-NEXT: [[ALIGN_NEG:%.+]] = sub i64 0, [[ALIGN]]
// CHECK-NEXT: [[SIZE:%.+]] = and i64 [[MAXSIZE]], [[ALIGN_NEG]]

// CHECK-NEXT: store i64 [[ALIGN]], ptr [[LAYOUT]], align 8
// CHECK-NEXT: [[LAYOUT_SIZEP:%.+]] = getelementptr inbounds i8, ptr [[LAYOUT]], i64 8
// CHECK-NEXT: store i64 [[SIZE]], ptr [[LAYOUT_SIZEP]], align 8
// CHECK-NOT: store
layout_of_val(ptr)
}

// CHECK-LABEL: declare

// CHECK: [[ALIGN_RANGE]] = !{i64 1, i64 -[[#0x7FFFFFFFFFFFFFFF]]
// CHECK: [[SIZE_RANGE]] = !{i64 0, i64 -[[#0x8000000000000000]]
Loading
Loading