Skip to content

Commit e392337

Browse files
committed
Auto merge of #102795 - lukas-code:constify-is-aligned-via-align-offset, r=oli-obk
Constify `is_aligned` via `align_offset` Alternative to rust-lang/rust#102753 Make `align_offset` work in const eval (and not always return `usize::MAX`) and then use that to constify `is_aligned{_to}`. Tracking Issue: rust-lang/rust#104203
2 parents 7f464be + 19dbb98 commit e392337

File tree

7 files changed

+786
-39
lines changed

7 files changed

+786
-39
lines changed

core/src/intrinsics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,7 @@ extern "rust-intrinsic" {
18511851
/// `x % y != 0` or `y == 0` or `x == T::MIN && y == -1`
18521852
///
18531853
/// This intrinsic does not have a stable counterpart.
1854+
#[rustc_const_unstable(feature = "const_exact_div", issue = "none")]
18541855
pub fn exact_div<T: Copy>(x: T, y: T) -> T;
18551856

18561857
/// Performs an unchecked division, resulting in undefined behavior

core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
#![feature(const_cmp)]
110110
#![feature(const_discriminant)]
111111
#![feature(const_eval_select)]
112+
#![feature(const_exact_div)]
112113
#![feature(const_float_bits_conv)]
113114
#![feature(const_float_classify)]
114115
#![feature(const_fmt_arguments_new)]
@@ -129,6 +130,7 @@
129130
#![feature(const_option)]
130131
#![feature(const_option_ext)]
131132
#![feature(const_pin)]
133+
#![feature(const_pointer_is_aligned)]
132134
#![feature(const_ptr_sub_ptr)]
133135
#![feature(const_replace)]
134136
#![feature(const_result_drop)]

core/src/ptr/const_ptr.rs

Lines changed: 241 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,8 @@ impl<T: ?Sized> *const T {
13201320
/// }
13211321
/// # }
13221322
/// ```
1323+
#[must_use]
1324+
#[inline]
13231325
#[stable(feature = "align_offset", since = "1.36.0")]
13241326
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
13251327
pub const fn align_offset(self, align: usize) -> usize
@@ -1330,32 +1332,149 @@ impl<T: ?Sized> *const T {
13301332
panic!("align_offset: align is not a power-of-two");
13311333
}
13321334

1333-
fn rt_impl<T>(p: *const T, align: usize) -> usize {
1334-
// SAFETY: `align` has been checked to be a power of 2 above
1335-
unsafe { align_offset(p, align) }
1336-
}
1335+
#[cfg(bootstrap)]
1336+
{
1337+
fn rt_impl<T>(p: *const T, align: usize) -> usize {
1338+
// SAFETY: `align` has been checked to be a power of 2 above
1339+
unsafe { align_offset(p, align) }
1340+
}
1341+
1342+
const fn ctfe_impl<T>(_: *const T, _: usize) -> usize {
1343+
usize::MAX
1344+
}
13371345

1338-
const fn ctfe_impl<T>(_: *const T, _: usize) -> usize {
1339-
usize::MAX
1346+
// SAFETY:
1347+
// It is permissible for `align_offset` to always return `usize::MAX`,
1348+
// algorithm correctness can not depend on `align_offset` returning non-max values.
1349+
//
1350+
// As such the behaviour can't change after replacing `align_offset` with `usize::MAX`, only performance can.
1351+
unsafe { intrinsics::const_eval_select((self, align), ctfe_impl, rt_impl) }
13401352
}
13411353

1342-
// SAFETY:
1343-
// It is permissible for `align_offset` to always return `usize::MAX`,
1344-
// algorithm correctness can not depend on `align_offset` returning non-max values.
1345-
//
1346-
// As such the behaviour can't change after replacing `align_offset` with `usize::MAX`, only performance can.
1347-
unsafe { intrinsics::const_eval_select((self, align), ctfe_impl, rt_impl) }
1354+
#[cfg(not(bootstrap))]
1355+
{
1356+
// SAFETY: `align` has been checked to be a power of 2 above
1357+
unsafe { align_offset(self, align) }
1358+
}
13481359
}
13491360

13501361
/// Returns whether the pointer is properly aligned for `T`.
1362+
///
1363+
/// # Examples
1364+
///
1365+
/// Basic usage:
1366+
/// ```
1367+
/// #![feature(pointer_is_aligned)]
1368+
/// #![feature(pointer_byte_offsets)]
1369+
///
1370+
/// // On some platforms, the alignment of i32 is less than 4.
1371+
/// #[repr(align(4))]
1372+
/// struct AlignedI32(i32);
1373+
///
1374+
/// let data = AlignedI32(42);
1375+
/// let ptr = &data as *const AlignedI32;
1376+
///
1377+
/// assert!(ptr.is_aligned());
1378+
/// assert!(!ptr.wrapping_byte_add(1).is_aligned());
1379+
/// ```
1380+
///
1381+
/// # At compiletime
1382+
/// **Note: Alignment at compiletime is experimental and subject to change. See the
1383+
/// [tracking issue] for details.**
1384+
///
1385+
/// At compiletime, the compiler may not know where a value will end up in memory.
1386+
/// Calling this function on a pointer created from a reference at compiletime will only
1387+
/// return `true` if the pointer is guaranteed to be aligned. This means that the pointer
1388+
/// is never aligned if cast to a type with a stricter alignment than the reference's
1389+
/// underlying allocation.
1390+
///
1391+
#[cfg_attr(bootstrap, doc = "```ignore")]
1392+
#[cfg_attr(not(bootstrap), doc = "```")]
1393+
/// #![feature(pointer_is_aligned)]
1394+
/// #![feature(const_pointer_is_aligned)]
1395+
///
1396+
/// // On some platforms, the alignment of primitives is less than their size.
1397+
/// #[repr(align(4))]
1398+
/// struct AlignedI32(i32);
1399+
/// #[repr(align(8))]
1400+
/// struct AlignedI64(i64);
1401+
///
1402+
/// const _: () = {
1403+
/// let data = AlignedI32(42);
1404+
/// let ptr = &data as *const AlignedI32;
1405+
/// assert!(ptr.is_aligned());
1406+
///
1407+
/// // At runtime either `ptr1` or `ptr2` would be aligned, but at compiletime neither is aligned.
1408+
/// let ptr1 = ptr.cast::<AlignedI64>();
1409+
/// let ptr2 = ptr.wrapping_add(1).cast::<AlignedI64>();
1410+
/// assert!(!ptr1.is_aligned());
1411+
/// assert!(!ptr2.is_aligned());
1412+
/// };
1413+
/// ```
1414+
///
1415+
/// Due to this behavior, it is possible that a runtime pointer derived from a compiletime
1416+
/// pointer is aligned, even if the compiletime pointer wasn't aligned.
1417+
///
1418+
#[cfg_attr(bootstrap, doc = "```ignore")]
1419+
#[cfg_attr(not(bootstrap), doc = "```")]
1420+
/// #![feature(pointer_is_aligned)]
1421+
/// #![feature(const_pointer_is_aligned)]
1422+
///
1423+
/// // On some platforms, the alignment of primitives is less than their size.
1424+
/// #[repr(align(4))]
1425+
/// struct AlignedI32(i32);
1426+
/// #[repr(align(8))]
1427+
/// struct AlignedI64(i64);
1428+
///
1429+
/// // At compiletime, neither `COMPTIME_PTR` nor `COMPTIME_PTR + 1` is aligned.
1430+
/// const COMPTIME_PTR: *const AlignedI32 = &AlignedI32(42);
1431+
/// const _: () = assert!(!COMPTIME_PTR.cast::<AlignedI64>().is_aligned());
1432+
/// const _: () = assert!(!COMPTIME_PTR.wrapping_add(1).cast::<AlignedI64>().is_aligned());
1433+
///
1434+
/// // At runtime, either `runtime_ptr` or `runtime_ptr + 1` is aligned.
1435+
/// let runtime_ptr = COMPTIME_PTR;
1436+
/// assert_ne!(
1437+
/// runtime_ptr.cast::<AlignedI64>().is_aligned(),
1438+
/// runtime_ptr.wrapping_add(1).cast::<AlignedI64>().is_aligned(),
1439+
/// );
1440+
/// ```
1441+
///
1442+
/// If a pointer is created from a fixed address, this function behaves the same during
1443+
/// runtime and compiletime.
1444+
///
1445+
#[cfg_attr(bootstrap, doc = "```ignore")]
1446+
#[cfg_attr(not(bootstrap), doc = "```")]
1447+
/// #![feature(pointer_is_aligned)]
1448+
/// #![feature(const_pointer_is_aligned)]
1449+
///
1450+
/// // On some platforms, the alignment of primitives is less than their size.
1451+
/// #[repr(align(4))]
1452+
/// struct AlignedI32(i32);
1453+
/// #[repr(align(8))]
1454+
/// struct AlignedI64(i64);
1455+
///
1456+
/// const _: () = {
1457+
/// let ptr = 40 as *const AlignedI32;
1458+
/// assert!(ptr.is_aligned());
1459+
///
1460+
/// // For pointers with a known address, runtime and compiletime behavior are identical.
1461+
/// let ptr1 = ptr.cast::<AlignedI64>();
1462+
/// let ptr2 = ptr.wrapping_add(1).cast::<AlignedI64>();
1463+
/// assert!(ptr1.is_aligned());
1464+
/// assert!(!ptr2.is_aligned());
1465+
/// };
1466+
/// ```
1467+
///
1468+
/// [tracking issue]: https://github.com/rust-lang/rust/issues/104203
13511469
#[must_use]
13521470
#[inline]
13531471
#[unstable(feature = "pointer_is_aligned", issue = "96284")]
1354-
pub fn is_aligned(self) -> bool
1472+
#[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "104203")]
1473+
pub const fn is_aligned(self) -> bool
13551474
where
13561475
T: Sized,
13571476
{
1358-
self.is_aligned_to(core::mem::align_of::<T>())
1477+
self.is_aligned_to(mem::align_of::<T>())
13591478
}
13601479

13611480
/// Returns whether the pointer is aligned to `align`.
@@ -1366,16 +1485,121 @@ impl<T: ?Sized> *const T {
13661485
/// # Panics
13671486
///
13681487
/// The function panics if `align` is not a power-of-two (this includes 0).
1488+
///
1489+
/// # Examples
1490+
///
1491+
/// Basic usage:
1492+
/// ```
1493+
/// #![feature(pointer_is_aligned)]
1494+
/// #![feature(pointer_byte_offsets)]
1495+
///
1496+
/// // On some platforms, the alignment of i32 is less than 4.
1497+
/// #[repr(align(4))]
1498+
/// struct AlignedI32(i32);
1499+
///
1500+
/// let data = AlignedI32(42);
1501+
/// let ptr = &data as *const AlignedI32;
1502+
///
1503+
/// assert!(ptr.is_aligned_to(1));
1504+
/// assert!(ptr.is_aligned_to(2));
1505+
/// assert!(ptr.is_aligned_to(4));
1506+
///
1507+
/// assert!(ptr.wrapping_byte_add(2).is_aligned_to(2));
1508+
/// assert!(!ptr.wrapping_byte_add(2).is_aligned_to(4));
1509+
///
1510+
/// assert_ne!(ptr.is_aligned_to(8), ptr.wrapping_add(1).is_aligned_to(8));
1511+
/// ```
1512+
///
1513+
/// # At compiletime
1514+
/// **Note: Alignment at compiletime is experimental and subject to change. See the
1515+
/// [tracking issue] for details.**
1516+
///
1517+
/// At compiletime, the compiler may not know where a value will end up in memory.
1518+
/// Calling this function on a pointer created from a reference at compiletime will only
1519+
/// return `true` if the pointer is guaranteed to be aligned. This means that the pointer
1520+
/// cannot be stricter aligned than the reference's underlying allocation.
1521+
///
1522+
#[cfg_attr(bootstrap, doc = "```ignore")]
1523+
#[cfg_attr(not(bootstrap), doc = "```")]
1524+
/// #![feature(pointer_is_aligned)]
1525+
/// #![feature(const_pointer_is_aligned)]
1526+
///
1527+
/// // On some platforms, the alignment of i32 is less than 4.
1528+
/// #[repr(align(4))]
1529+
/// struct AlignedI32(i32);
1530+
///
1531+
/// const _: () = {
1532+
/// let data = AlignedI32(42);
1533+
/// let ptr = &data as *const AlignedI32;
1534+
///
1535+
/// assert!(ptr.is_aligned_to(1));
1536+
/// assert!(ptr.is_aligned_to(2));
1537+
/// assert!(ptr.is_aligned_to(4));
1538+
///
1539+
/// // At compiletime, we know for sure that the pointer isn't aligned to 8.
1540+
/// assert!(!ptr.is_aligned_to(8));
1541+
/// assert!(!ptr.wrapping_add(1).is_aligned_to(8));
1542+
/// };
1543+
/// ```
1544+
///
1545+
/// Due to this behavior, it is possible that a runtime pointer derived from a compiletime
1546+
/// pointer is aligned, even if the compiletime pointer wasn't aligned.
1547+
///
1548+
#[cfg_attr(bootstrap, doc = "```ignore")]
1549+
#[cfg_attr(not(bootstrap), doc = "```")]
1550+
/// #![feature(pointer_is_aligned)]
1551+
/// #![feature(const_pointer_is_aligned)]
1552+
///
1553+
/// // On some platforms, the alignment of i32 is less than 4.
1554+
/// #[repr(align(4))]
1555+
/// struct AlignedI32(i32);
1556+
///
1557+
/// // At compiletime, neither `COMPTIME_PTR` nor `COMPTIME_PTR + 1` is aligned.
1558+
/// const COMPTIME_PTR: *const AlignedI32 = &AlignedI32(42);
1559+
/// const _: () = assert!(!COMPTIME_PTR.is_aligned_to(8));
1560+
/// const _: () = assert!(!COMPTIME_PTR.wrapping_add(1).is_aligned_to(8));
1561+
///
1562+
/// // At runtime, either `runtime_ptr` or `runtime_ptr + 1` is aligned.
1563+
/// let runtime_ptr = COMPTIME_PTR;
1564+
/// assert_ne!(
1565+
/// runtime_ptr.is_aligned_to(8),
1566+
/// runtime_ptr.wrapping_add(1).is_aligned_to(8),
1567+
/// );
1568+
/// ```
1569+
///
1570+
/// If a pointer is created from a fixed address, this function behaves the same during
1571+
/// runtime and compiletime.
1572+
///
1573+
#[cfg_attr(bootstrap, doc = "```ignore")]
1574+
#[cfg_attr(not(bootstrap), doc = "```")]
1575+
/// #![feature(pointer_is_aligned)]
1576+
/// #![feature(const_pointer_is_aligned)]
1577+
///
1578+
/// const _: () = {
1579+
/// let ptr = 40 as *const u8;
1580+
/// assert!(ptr.is_aligned_to(1));
1581+
/// assert!(ptr.is_aligned_to(2));
1582+
/// assert!(ptr.is_aligned_to(4));
1583+
/// assert!(ptr.is_aligned_to(8));
1584+
/// assert!(!ptr.is_aligned_to(16));
1585+
/// };
1586+
/// ```
1587+
///
1588+
/// [tracking issue]: https://github.com/rust-lang/rust/issues/104203
13691589
#[must_use]
13701590
#[inline]
13711591
#[unstable(feature = "pointer_is_aligned", issue = "96284")]
1372-
pub fn is_aligned_to(self, align: usize) -> bool {
1592+
#[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "104203")]
1593+
pub const fn is_aligned_to(self, align: usize) -> bool {
13731594
if !align.is_power_of_two() {
13741595
panic!("is_aligned_to: align is not a power-of-two");
13751596
}
13761597

1377-
// Cast is needed for `T: !Sized`
1378-
self.cast::<u8>().addr() & align - 1 == 0
1598+
// We can't use the address of `self` in a `const fn`, so we use `align_offset` instead.
1599+
// The cast to `()` is used to
1600+
// 1. deal with fat pointers; and
1601+
// 2. ensure that `align_offset` doesn't actually try to compute an offset.
1602+
self.cast::<()>().align_offset(align) == 0
13791603
}
13801604
}
13811605

core/src/ptr/mod.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,10 +1574,14 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15741574

15751575
/// Align pointer `p`.
15761576
///
1577-
/// Calculate offset (in terms of elements of `stride` stride) that has to be applied
1577+
/// Calculate offset (in terms of elements of `size_of::<T>()` stride) that has to be applied
15781578
/// to pointer `p` so that pointer `p` would get aligned to `a`.
15791579
///
1580-
/// Note: This implementation has been carefully tailored to not panic. It is UB for this to panic.
1580+
/// # Safety
1581+
/// `a` must be a power of two.
1582+
///
1583+
/// # Notes
1584+
/// This implementation has been carefully tailored to not panic. It is UB for this to panic.
15811585
/// The only real change that can be made here is change of `INV_TABLE_MOD_16` and associated
15821586
/// constants.
15831587
///
@@ -1587,7 +1591,7 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15871591
///
15881592
/// Any questions go to @nagisa.
15891593
#[lang = "align_offset"]
1590-
pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
1594+
pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
15911595
// FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=
15921596
// 1, where the method versions of these operations are not inlined.
15931597
use intrinsics::{
@@ -1604,7 +1608,7 @@ pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
16041608
///
16051609
/// Implementation of this function shall not panic. Ever.
16061610
#[inline]
1607-
unsafe fn mod_inv(x: usize, m: usize) -> usize {
1611+
const unsafe fn mod_inv(x: usize, m: usize) -> usize {
16081612
/// Multiplicative modular inverse table modulo 2⁴ = 16.
16091613
///
16101614
/// Note, that this table does not contain values where inverse does not exist (i.e., for
@@ -1646,8 +1650,14 @@ pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
16461650
inverse & m_minus_one
16471651
}
16481652

1649-
let addr = p.addr();
16501653
let stride = mem::size_of::<T>();
1654+
1655+
// SAFETY: This is just an inlined `p.addr()` (which is not
1656+
// a `const fn` so we cannot call it).
1657+
// During const eval, we hook this function to ensure that the pointer never
1658+
// has provenance, making this sound.
1659+
let addr: usize = unsafe { mem::transmute(p) };
1660+
16511661
// SAFETY: `a` is a power-of-two, therefore non-zero.
16521662
let a_minus_one = unsafe { unchecked_sub(a, 1) };
16531663

0 commit comments

Comments
 (0)