Skip to content

Add contracts for all functions in Alignment #136578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2605,6 +2605,7 @@ pub const fn contract_checks() -> bool {
// doesn't honor `#[allow_internal_unstable]`, so for the const feature gate we use the user-facing
// `contracts` feature rather than the perma-unstable `contracts_internals`
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
#[miri::intrinsic_fallback_is_spec]
Copy link
Member

Choose a reason for hiding this comment

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

It seems that no backend actually overwrites this intrinsic. Why is it an intrinsic in the first place...? (Same for contract_check_ensures.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Other verification tools might tho. I need to ping the compiler team about design review of the existing design and long term solution.

#[lang = "contract_check_requires"]
#[rustc_intrinsic]
pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
Expand Down Expand Up @@ -2632,6 +2633,7 @@ pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
// `contracts` feature rather than the perma-unstable `contracts_internals`.
// Const-checking doesn't honor allow_internal_unstable logic used by contract expansion.
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
#[miri::intrinsic_fallback_is_spec]
#[lang = "contract_check_ensures"]
#[rustc_intrinsic]
pub const fn contract_check_ensures<C: Fn(&Ret) -> bool + Copy, Ret>(cond: C, ret: Ret) -> Ret {
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
#![feature(const_cmp)]
#![feature(const_destruct)]
#![feature(const_eval_select)]
#![feature(contracts)]
#![feature(core_intrinsics)]
#![feature(coverage_attribute)]
#![feature(disjoint_bitor)]
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,8 @@ pub const fn align_of<T>() -> usize {
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_align_of_val", since = "1.85.0")]
#[rustc_allow_const_fn_unstable(contracts)]
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should add rustc_const_stable_indirect to contract functions instead. @rust-lang/wg-const-eval?

Copy link
Member

@RalfJung RalfJung Mar 19, 2025

Choose a reason for hiding this comment

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

Which functions would that affect?

Ah, #136925 anyway needs to be resolved first.

Copy link
Member

Choose a reason for hiding this comment

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

I'd still like to find a solution that does not require rustc_allow_const_fn_unstable. As @celinval suggested, we should try to mark the contract functions as #[rustc_const_stable_indirect]; if that works, that's a better solution.

Copy link
Contributor

Choose a reason for hiding this comment

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

@tautschnig did you try to make this change?

#[core::contracts::ensures(|result: &usize| result.is_power_of_two())]
pub const fn align_of_val<T: ?Sized>(val: &T) -> usize {
// SAFETY: val is a reference, so it's a valid raw pointer
unsafe { intrinsics::align_of_val(val) }
Expand Down
30 changes: 30 additions & 0 deletions library/core/src/ptr/alignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ impl Alignment {
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[must_use]
#[rustc_allow_const_fn_unstable(contracts)]
#[core::contracts::ensures(|result: &Alignment| result.as_usize().is_power_of_two())]
pub const fn of<T>() -> Self {
// This can't actually panic since type alignment is always a power of two.
const { Alignment::new(align_of::<T>()).unwrap() }
Expand All @@ -56,6 +58,11 @@ impl Alignment {
/// Note that `0` is not a power of two, nor a valid alignment.
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_allow_const_fn_unstable(contracts)]
#[core::contracts::ensures(
move |result: &Option<Alignment>|
align.is_power_of_two() == result.is_some() &&
(result.is_none() || result.unwrap().as_usize() == align))]
pub const fn new(align: usize) -> Option<Self> {
if align.is_power_of_two() {
// SAFETY: Just checked it only has one bit set
Expand All @@ -76,6 +83,12 @@ impl Alignment {
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[track_caller]
#[rustc_allow_const_fn_unstable(contracts)]
#[allow(unused_parens)]
#[core::contracts::requires(align.is_power_of_two())]
#[core::contracts::ensures(
move |result: &Alignment|
result.as_usize() == align && result.as_usize().is_power_of_two())]
pub const unsafe fn new_unchecked(align: usize) -> Self {
assert_unsafe_precondition!(
check_language_ub,
Expand All @@ -91,13 +104,19 @@ impl Alignment {
/// Returns the alignment as a [`usize`].
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_allow_const_fn_unstable(contracts)]
#[core::contracts::ensures(|result: &usize| result.is_power_of_two())]
pub const fn as_usize(self) -> usize {
self.0 as usize
}

/// Returns the alignment as a <code>[NonZero]<[usize]></code>.
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_allow_const_fn_unstable(contracts)]
#[core::contracts::ensures(
move |result: &NonZero<usize>|
result.get().is_power_of_two() && result.get() == self.as_usize())]
pub const fn as_nonzero(self) -> NonZero<usize> {
// This transmutes directly to avoid the UbCheck in `NonZero::new_unchecked`
// since there's no way for the user to trip that check anyway -- the
Expand All @@ -123,6 +142,12 @@ impl Alignment {
/// ```
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
#[allow(unused_parens)]
#[core::contracts::requires(self.as_usize().is_power_of_two())]
#[core::contracts::ensures(
move |result: &u32|
*result < usize::BITS && (1usize << *result) == self.as_usize())]
pub const fn log2(self) -> u32 {
self.as_nonzero().trailing_zeros()
}
Expand Down Expand Up @@ -152,6 +177,11 @@ impl Alignment {
/// ```
#[unstable(feature = "ptr_alignment_type", issue = "102070")]
#[inline]
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
#[core::contracts::ensures(
move |result: &usize|
*result > 0 && *result == !(self.as_usize() -1) &&
self.as_usize() & *result == self.as_usize())]
pub const fn mask(self) -> usize {
// SAFETY: The alignment is always nonzero, and therefore decrementing won't overflow.
!(unsafe { self.as_usize().unchecked_sub(1) })
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/tests/pass/shims/time-with-isolation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn test_time_passes() {
// if `NANOSECONDS_PER_BASIC_BLOCK` changes. It may also need updating if the standard library
// code that runs in the loop above changes.
assert!(diff.as_millis() > 5);
assert!(diff.as_millis() < 20);
assert!(diff.as_millis() < 25);
}

fn test_block_for_one_second() {
Expand Down
4 changes: 2 additions & 2 deletions tests/codegen-llvm/cross-crate-inlining/auxiliary/leaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

// This function *looks* like it contains a call, but that call will be optimized out by MIR
// optimizations.
pub fn leaf_fn() -> String {
String::new()
pub fn leaf_fn() -> bool {
Some(0).is_some()

Choose a reason for hiding this comment

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

Is this the way we want to handle the test failure? Surely, we don't want contracts to affect the inlining?

Copy link
Member

Choose a reason for hiding this comment

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

Well, I think UB checks already do.

IMO the important case is that trivial getters and setters that literally only do a field access get made cross-crate-inlineable. Also managing to detect functions that optimize to just a load or store is gravy. It's impossible in the general case, because of the limitations of the current compilation model and query system. And it's sometimes undesirable because making a function cross-crate-inlineable increases the amount of code that dependent crates need to compile, which can regress incremental build times.

... Wouldn't hurt to have that sort of explanation in the codebase.

Copy link
Member

Choose a reason for hiding this comment

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

In addition, I think there's a possible future extension to the cross-crate-inlineable analysis that excludes code underneath a UB check or contract check from the cost model.

Choose a reason for hiding this comment

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

I believe disabled contracts should have zero runtime penalties, since they serve a different purpose to UB checks or any other form of existing runtime assertion. Their role is more akin to doctests: documentation that can be executed for testing purposes. Additionally contracts are a means to facilitate formal verification.

These are the goals of contracts stated by the draft RFC.

  1. They form precise, machine readable documentation of the expected behaviour of your code.
  2. They can be automatically converted into assert! checks, and then validated using your existing unit, property, fuzz, and integration tests.
  3. They form the specification for formal verification tools. In cases where these tools can prove the absence of bugs, the checks can be elided entirely from the code, giving you safety with no runtime cost. These formally verified checks can even form the basis of assume statements, enabling additional optimization within the compiler (e.g. allowing the compiler to remove bounds checks on accesses that can be formally proven to be safe).

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure you understood what I was saying, so I'm rephrasing. UB checks currently have an impact on optimizations (that look exactly like the diff we are commenting on) even when they are completely disabled at runtime. In an ideal world, they wouldn't. But the actual consequences here are very minimal in practice, so we haven't yet invested very heavily in making the compiler perfect.

Copy link
Member

@RalfJung RalfJung Aug 23, 2025

Choose a reason for hiding this comment

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

For some reason it's not able to constant propagate the None so that contract_check_requires(__postcond, body) expands to just body.

contract_check_requires has signature fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) so I can't match that up with the code you are posting here. Do you mean contract_check_ensures? But there's no Option argument there either...

But anyway it seems you want contract_check_ensures to be inlined but intrinsics do not get inlined since backends may want to plug in their own implementation. This may need a custom optimization pass. Right now though it's unclear to me why this is an intrinsic in the first place, and your example code doesn't match the actual signatures, so what this needs most is a step back and a plan -- blind experimentation isn't going to get us there.

Copy link

@dawidl022 dawidl022 Aug 23, 2025

Choose a reason for hiding this comment

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

Yes, sorry, I meant contract_check_ensures (edited my previous comment now). I'm referring to my code in #144438, which @celinval linked to.

Copy link
Member

Choose a reason for hiding this comment

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

Ah... please don't spread the discussion over multiple PRs, that will be impossible to follow.

Copy link
Member

Choose a reason for hiding this comment

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

Probably best to mark this PR here as a draft and suspend it until #144438 is done.

Copy link
Member

Choose a reason for hiding this comment

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

if there was even a slight possibility of runtime penalties.

I think this is an unreasonable criteria, and I tried to explain why by referring to the if T::IS_ZST pattern.

}

// This function contains a call, even after MIR optimizations. It is only eligible for
Expand Down
2 changes: 1 addition & 1 deletion tests/codegen-llvm/cross-crate-inlining/leaf-inlining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extern crate leaf;

// Check that we inline a leaf cross-crate call
#[no_mangle]
pub fn leaf_outer() -> String {
pub fn leaf_outer() -> bool {
// CHECK-NOT: call {{.*}}leaf_fn
leaf::leaf_fn()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@
scope 4 (inlined Unique::<[bool; 0]>::dangling) {
let mut _5: std::ptr::NonNull<[bool; 0]>;
scope 5 (inlined NonNull::<[bool; 0]>::dangling) {
let mut _6: std::num::NonZero<usize>;
let _6: std::ptr::Alignment;
let mut _7: std::num::NonZero<usize>;
scope 6 {
scope 8 (inlined std::ptr::Alignment::as_nonzero) {
scope 10 (inlined std::ptr::Alignment::as_nonzero) {
let mut _8: {closure@std::ptr::Alignment::as_nonzero::{closure#0}};
let mut _9: std::num::NonZero<usize>;
scope 11 {
}
scope 12 (inlined core::contracts::build_check_ensures::<NonZero<usize>, {closure@std::ptr::Alignment::as_nonzero::{closure#0}}>) {
}
}
scope 9 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _7: *const [bool; 0];
scope 10 {
scope 13 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _10: *const [bool; 0];
scope 14 {
}
scope 11 (inlined NonZero::<usize>::get) {
scope 15 (inlined NonZero::<usize>::get) {
}
scope 12 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 13 (inlined without_provenance_mut::<[bool; 0]>) {
scope 16 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 17 (inlined without_provenance_mut::<[bool; 0]>) {
}
}
}
}
scope 7 (inlined std::ptr::Alignment::of::<[bool; 0]>) {
scope 8 {
}
scope 9 (inlined core::contracts::build_check_ensures::<std::ptr::Alignment, {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}>) {
}
}
}
}
Expand All @@ -45,33 +56,42 @@
StorageLive(_4);
StorageLive(_5);
StorageLive(_6);
_6 = const NonZero::<usize>(core::num::niche_types::NonZeroUsizeInner(1_usize));
_6 = contract_check_ensures::<{closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, std::ptr::Alignment>(const ZeroSized: {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, const std::ptr::Alignment::of::<[bool; 0]>::{constant#0}) -> [return: bb2, unwind unreachable];
}

bb1: {
StorageDead(_1);
return;
}

bb2: {
StorageLive(_7);
_7 = const {0x1 as *const [bool; 0]};
_5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }};
StorageLive(_8);
_8 = {closure@$SRC_DIR/core/src/ptr/alignment.rs:LL:COL} { 0: copy _6 };
StorageLive(_9);
_9 = copy _6 as std::num::NonZero<usize> (Transmute);
_7 = contract_check_ensures::<{closure@std::ptr::Alignment::as_nonzero::{closure#0}}, NonZero<usize>>(move _8, move _9) -> [return: bb3, unwind unreachable];
}

bb3: {
StorageDead(_9);
StorageDead(_8);
StorageLive(_10);
_10 = copy _7 as *const [bool; 0] (Transmute);
_5 = NonNull::<[bool; 0]> { pointer: copy _10 };
StorageDead(_10);
StorageDead(_7);
StorageDead(_6);
_4 = const Unique::<[bool; 0]> {{ pointer: NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}, _marker: PhantomData::<[bool; 0]> }};
_4 = Unique::<[bool; 0]> { pointer: move _5, _marker: const PhantomData::<[bool; 0]> };
StorageDead(_5);
_3 = const Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC0, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }};
_3 = move _4 as std::ptr::Unique<[bool]> (PointerCoercion(Unsize, Implicit));
StorageDead(_4);
_2 = const Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC1, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global);
_2 = Box::<[bool]>(copy _3, const std::alloc::Global);
StorageDead(_3);
_1 = const A {{ foo: Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC2, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global) }};
_1 = A { foo: move _2 };
StorageDead(_2);
_0 = const ();
drop(_1) -> [return: bb1, unwind unreachable];
}

bb1: {
StorageDead(_1);
return;
}
}

ALLOC2 (size: 8, align: 4) { .. }

ALLOC1 (size: 8, align: 4) { .. }

ALLOC0 (size: 8, align: 4) { .. }

Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@
scope 4 (inlined Unique::<[bool; 0]>::dangling) {
let mut _5: std::ptr::NonNull<[bool; 0]>;
scope 5 (inlined NonNull::<[bool; 0]>::dangling) {
let mut _6: std::num::NonZero<usize>;
let _6: std::ptr::Alignment;
let mut _7: std::num::NonZero<usize>;
scope 6 {
scope 8 (inlined std::ptr::Alignment::as_nonzero) {
scope 10 (inlined std::ptr::Alignment::as_nonzero) {
let mut _8: {closure@std::ptr::Alignment::as_nonzero::{closure#0}};
let mut _9: std::num::NonZero<usize>;
scope 11 {
}
scope 12 (inlined core::contracts::build_check_ensures::<NonZero<usize>, {closure@std::ptr::Alignment::as_nonzero::{closure#0}}>) {
}
}
scope 9 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _7: *const [bool; 0];
scope 10 {
scope 13 (inlined NonNull::<[bool; 0]>::without_provenance) {
let _10: *const [bool; 0];
scope 14 {
}
scope 11 (inlined NonZero::<usize>::get) {
scope 15 (inlined NonZero::<usize>::get) {
}
scope 12 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 13 (inlined without_provenance_mut::<[bool; 0]>) {
scope 16 (inlined std::ptr::without_provenance::<[bool; 0]>) {
scope 17 (inlined without_provenance_mut::<[bool; 0]>) {
}
}
}
}
scope 7 (inlined std::ptr::Alignment::of::<[bool; 0]>) {
scope 8 {
}
scope 9 (inlined core::contracts::build_check_ensures::<std::ptr::Alignment, {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}>) {
}
}
}
}
Expand All @@ -45,22 +56,7 @@
StorageLive(_4);
StorageLive(_5);
StorageLive(_6);
_6 = const NonZero::<usize>(core::num::niche_types::NonZeroUsizeInner(1_usize));
StorageLive(_7);
_7 = const {0x1 as *const [bool; 0]};
_5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }};
StorageDead(_7);
StorageDead(_6);
_4 = const Unique::<[bool; 0]> {{ pointer: NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}, _marker: PhantomData::<[bool; 0]> }};
StorageDead(_5);
_3 = const Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC0, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }};
StorageDead(_4);
_2 = const Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC1, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global);
StorageDead(_3);
_1 = const A {{ foo: Box::<[bool]>(Unique::<[bool]> {{ pointer: NonNull::<[bool]> {{ pointer: Indirect { alloc_id: ALLOC2, offset: Size(0 bytes) }: *const [bool] }}, _marker: PhantomData::<[bool]> }}, std::alloc::Global) }};
StorageDead(_2);
_0 = const ();
drop(_1) -> [return: bb1, unwind: bb2];
_6 = contract_check_ensures::<{closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, std::ptr::Alignment>(const ZeroSized: {closure@std::ptr::Alignment::of<[bool; 0]>::{closure#0}}, const std::ptr::Alignment::of::<[bool; 0]>::{constant#0}) -> [return: bb3, unwind continue];
}

bb1: {
Expand All @@ -71,11 +67,35 @@
bb2 (cleanup): {
resume;
}
}

ALLOC2 (size: 8, align: 4) { .. }

ALLOC1 (size: 8, align: 4) { .. }
bb3: {
StorageLive(_7);
StorageLive(_8);
_8 = {closure@$SRC_DIR/core/src/ptr/alignment.rs:LL:COL} { 0: copy _6 };
StorageLive(_9);
_9 = copy _6 as std::num::NonZero<usize> (Transmute);
_7 = contract_check_ensures::<{closure@std::ptr::Alignment::as_nonzero::{closure#0}}, NonZero<usize>>(move _8, move _9) -> [return: bb4, unwind continue];
}

ALLOC0 (size: 8, align: 4) { .. }
bb4: {
StorageDead(_9);
StorageDead(_8);
StorageLive(_10);
_10 = copy _7 as *const [bool; 0] (Transmute);
_5 = NonNull::<[bool; 0]> { pointer: copy _10 };
StorageDead(_10);
StorageDead(_7);
StorageDead(_6);
_4 = Unique::<[bool; 0]> { pointer: move _5, _marker: const PhantomData::<[bool; 0]> };
StorageDead(_5);
_3 = move _4 as std::ptr::Unique<[bool]> (PointerCoercion(Unsize, Implicit));
StorageDead(_4);
_2 = Box::<[bool]>(copy _3, const std::alloc::Global);
StorageDead(_3);
_1 = A { foo: move _2 };
StorageDead(_2);
_0 = const ();
drop(_1) -> [return: bb1, unwind: bb2];
}
}

Loading
Loading