Skip to content

Commit c5d82ed

Browse files
committedNov 19, 2022
Auto merge of #102795 - lukas-code:constify-is-aligned-via-align-offset, r=oli-obk
Constify `is_aligned` via `align_offset` Alternative to #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: #104203
·
1.88.01.67.0
2 parents 2a43428 + c9c017d commit c5d82ed

File tree

11 files changed

+984
-97
lines changed

11 files changed

+984
-97
lines changed
 

‎compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 135 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use rustc_hir::def::DefKind;
2+
use rustc_hir::LangItem;
23
use rustc_middle::mir;
4+
use rustc_middle::mir::interpret::PointerArithmetic;
5+
use rustc_middle::ty::layout::FnAbiOf;
36
use rustc_middle::ty::{self, Ty, TyCtxt};
47
use std::borrow::Borrow;
58
use std::hash::Hash;
9+
use std::ops::ControlFlow;
610

711
use rustc_data_structures::fx::FxIndexMap;
812
use rustc_data_structures::fx::IndexEntry;
@@ -17,58 +21,12 @@ use rustc_target::abi::{Align, Size};
1721
use rustc_target::spec::abi::Abi as CallAbi;
1822

1923
use crate::interpret::{
20-
self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
21-
OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
24+
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
25+
InterpResult, OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
2226
};
2327

2428
use super::error::*;
2529

26-
impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> {
27-
/// "Intercept" a function call to a panic-related function
28-
/// because we have something special to do for it.
29-
/// If this returns successfully (`Ok`), the function should just be evaluated normally.
30-
fn hook_special_const_fn(
31-
&mut self,
32-
instance: ty::Instance<'tcx>,
33-
args: &[OpTy<'tcx>],
34-
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
35-
// All `#[rustc_do_not_const_check]` functions should be hooked here.
36-
let def_id = instance.def_id();
37-
38-
if Some(def_id) == self.tcx.lang_items().panic_display()
39-
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
40-
{
41-
// &str or &&str
42-
assert!(args.len() == 1);
43-
44-
let mut msg_place = self.deref_operand(&args[0])?;
45-
while msg_place.layout.ty.is_ref() {
46-
msg_place = self.deref_operand(&msg_place.into())?;
47-
}
48-
49-
let msg = Symbol::intern(self.read_str(&msg_place)?);
50-
let span = self.find_closest_untracked_caller_location();
51-
let (file, line, col) = self.location_triple_for_span(span);
52-
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
53-
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
54-
// For panic_fmt, call const_panic_fmt instead.
55-
if let Some(const_panic_fmt) = self.tcx.lang_items().const_panic_fmt() {
56-
return Ok(Some(
57-
ty::Instance::resolve(
58-
*self.tcx,
59-
ty::ParamEnv::reveal_all(),
60-
const_panic_fmt,
61-
self.tcx.intern_substs(&[]),
62-
)
63-
.unwrap()
64-
.unwrap(),
65-
));
66-
}
67-
}
68-
Ok(None)
69-
}
70-
}
71-
7230
/// Extra machine state for CTFE, and the Machine instance
7331
pub struct CompileTimeInterpreter<'mir, 'tcx> {
7432
/// For now, the number of terminators that can be evaluated before we throw a resource
@@ -191,6 +149,125 @@ impl interpret::MayLeak for ! {
191149
}
192150

193151
impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
152+
/// "Intercept" a function call, because we have something special to do for it.
153+
/// All `#[rustc_do_not_const_check]` functions should be hooked here.
154+
/// If this returns `Some` function, which may be `instance` or a different function with
155+
/// compatible arguments, then evaluation should continue with that function.
156+
/// If this returns `None`, the function call has been handled and the function has returned.
157+
fn hook_special_const_fn(
158+
&mut self,
159+
instance: ty::Instance<'tcx>,
160+
args: &[OpTy<'tcx>],
161+
dest: &PlaceTy<'tcx>,
162+
ret: Option<mir::BasicBlock>,
163+
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
164+
let def_id = instance.def_id();
165+
166+
if Some(def_id) == self.tcx.lang_items().panic_display()
167+
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
168+
{
169+
// &str or &&str
170+
assert!(args.len() == 1);
171+
172+
let mut msg_place = self.deref_operand(&args[0])?;
173+
while msg_place.layout.ty.is_ref() {
174+
msg_place = self.deref_operand(&msg_place.into())?;
175+
}
176+
177+
let msg = Symbol::intern(self.read_str(&msg_place)?);
178+
let span = self.find_closest_untracked_caller_location();
179+
let (file, line, col) = self.location_triple_for_span(span);
180+
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
181+
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
182+
// For panic_fmt, call const_panic_fmt instead.
183+
let const_def_id = self.tcx.require_lang_item(LangItem::ConstPanicFmt, None);
184+
let new_instance = ty::Instance::resolve(
185+
*self.tcx,
186+
ty::ParamEnv::reveal_all(),
187+
const_def_id,
188+
instance.substs,
189+
)
190+
.unwrap()
191+
.unwrap();
192+
193+
return Ok(Some(new_instance));
194+
} else if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
195+
// For align_offset, we replace the function call if the pointer has no address.
196+
match self.align_offset(instance, args, dest, ret)? {
197+
ControlFlow::Continue(()) => return Ok(Some(instance)),
198+
ControlFlow::Break(()) => return Ok(None),
199+
}
200+
}
201+
Ok(Some(instance))
202+
}
203+
204+
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
205+
/// may not have an address.
206+
///
207+
/// If `ptr` does have a known address, then we return `CONTINUE` and the function call should
208+
/// proceed as normal.
209+
///
210+
/// If `ptr` doesn't have an address, but its underlying allocation's alignment is at most
211+
/// `target_align`, then we call the function again with an dummy address relative to the
212+
/// allocation.
213+
///
214+
/// If `ptr` doesn't have an address and `target_align` is stricter than the underlying
215+
/// allocation's alignment, then we return `usize::MAX` immediately.
216+
fn align_offset(
217+
&mut self,
218+
instance: ty::Instance<'tcx>,
219+
args: &[OpTy<'tcx>],
220+
dest: &PlaceTy<'tcx>,
221+
ret: Option<mir::BasicBlock>,
222+
) -> InterpResult<'tcx, ControlFlow<()>> {
223+
assert_eq!(args.len(), 2);
224+
225+
let ptr = self.read_pointer(&args[0])?;
226+
let target_align = self.read_scalar(&args[1])?.to_machine_usize(self)?;
227+
228+
if !target_align.is_power_of_two() {
229+
throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
230+
}
231+
232+
match self.ptr_try_get_alloc_id(ptr) {
233+
Ok((alloc_id, offset, _extra)) => {
234+
let (_size, alloc_align, _kind) = self.get_alloc_info(alloc_id);
235+
236+
if target_align <= alloc_align.bytes() {
237+
// Extract the address relative to the allocation base that is definitely
238+
// sufficiently aligned and call `align_offset` again.
239+
let addr = ImmTy::from_uint(offset.bytes(), args[0].layout).into();
240+
let align = ImmTy::from_uint(target_align, args[1].layout).into();
241+
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
242+
243+
// We replace the entire entire function call with a "tail call".
244+
// Note that this happens before the frame of the original function
245+
// is pushed on the stack.
246+
self.eval_fn_call(
247+
FnVal::Instance(instance),
248+
(CallAbi::Rust, fn_abi),
249+
&[addr, align],
250+
/* with_caller_location = */ false,
251+
dest,
252+
ret,
253+
StackPopUnwind::NotAllowed,
254+
)?;
255+
Ok(ControlFlow::BREAK)
256+
} else {
257+
// Not alignable in const, return `usize::MAX`.
258+
let usize_max = Scalar::from_machine_usize(self.machine_usize_max(), self);
259+
self.write_scalar(usize_max, dest)?;
260+
self.return_to_block(ret)?;
261+
Ok(ControlFlow::BREAK)
262+
}
263+
}
264+
Err(_addr) => {
265+
// The pointer has an address, continue with function call.
266+
Ok(ControlFlow::CONTINUE)
267+
}
268+
}
269+
}
270+
194271
/// See documentation on the `ptr_guaranteed_cmp` intrinsic.
195272
fn guaranteed_cmp(&mut self, a: Scalar, b: Scalar) -> InterpResult<'tcx, u8> {
196273
Ok(match (a, b) {
@@ -271,8 +348,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
271348
instance: ty::Instance<'tcx>,
272349
_abi: CallAbi,
273350
args: &[OpTy<'tcx>],
274-
_dest: &PlaceTy<'tcx>,
275-
_ret: Option<mir::BasicBlock>,
351+
dest: &PlaceTy<'tcx>,
352+
ret: Option<mir::BasicBlock>,
276353
_unwind: StackPopUnwind, // unwinding is not supported in consts
277354
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
278355
debug!("find_mir_or_eval_fn: {:?}", instance);
@@ -291,7 +368,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
291368
}
292369
}
293370

294-
if let Some(new_instance) = ecx.hook_special_const_fn(instance, args)? {
371+
let Some(new_instance) = ecx.hook_special_const_fn(instance, args, dest, ret)? else {
372+
return Ok(None);
373+
};
374+
375+
if new_instance != instance {
295376
// We call another const fn instead.
296377
// However, we return the *original* instance to make backtraces work out
297378
// (and we hope this does not confuse the FnAbi checks too much).
@@ -300,13 +381,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
300381
new_instance,
301382
_abi,
302383
args,
303-
_dest,
304-
_ret,
384+
dest,
385+
ret,
305386
_unwind,
306387
)?
307388
.map(|(body, _instance)| (body, instance)));
308389
}
309390
}
391+
310392
// This is a const fn. Call it.
311393
Ok(Some((ecx.load_mir(instance.def, None)?, instance)))
312394
}

‎compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
243243
let discr_val = self.read_discriminant(&place.into())?.0;
244244
self.write_scalar(discr_val, dest)?;
245245
}
246+
sym::exact_div => {
247+
let l = self.read_immediate(&args[0])?;
248+
let r = self.read_immediate(&args[1])?;
249+
self.exact_div(&l, &r, dest)?;
250+
}
246251
sym::unchecked_shl
247252
| sym::unchecked_shr
248253
| sym::unchecked_add

‎library/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

‎library/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)]

‎library/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

‎library/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

‎library/core/src/ptr/mut_ptr.rs

Lines changed: 245 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,6 +1588,8 @@ impl<T: ?Sized> *mut T {
15881588
/// }
15891589
/// # }
15901590
/// ```
1591+
#[must_use]
1592+
#[inline]
15911593
#[stable(feature = "align_offset", since = "1.36.0")]
15921594
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
15931595
pub const fn align_offset(self, align: usize) -> usize
@@ -1598,32 +1600,151 @@ impl<T: ?Sized> *mut T {
15981600
panic!("align_offset: align is not a power-of-two");
15991601
}
16001602

1601-
fn rt_impl<T>(p: *mut T, align: usize) -> usize {
1602-
// SAFETY: `align` has been checked to be a power of 2 above
1603-
unsafe { align_offset(p, align) }
1603+
#[cfg(bootstrap)]
1604+
{
1605+
fn rt_impl<T>(p: *mut T, align: usize) -> usize {
1606+
// SAFETY: `align` has been checked to be a power of 2 above
1607+
unsafe { align_offset(p, align) }
1608+
}
1609+
1610+
const fn ctfe_impl<T>(_: *mut T, _: usize) -> usize {
1611+
usize::MAX
1612+
}
1613+
1614+
// SAFETY:
1615+
// It is permissible for `align_offset` to always return `usize::MAX`,
1616+
// algorithm correctness can not depend on `align_offset` returning non-max values.
1617+
//
1618+
// As such the behaviour can't change after replacing `align_offset` with `usize::MAX`, only performance can.
1619+
unsafe { intrinsics::const_eval_select((self, align), ctfe_impl, rt_impl) }
16041620
}
16051621

1606-
const fn ctfe_impl<T>(_: *mut T, _: usize) -> usize {
1607-
usize::MAX
1622+
#[cfg(not(bootstrap))]
1623+
{
1624+
// SAFETY: `align` has been checked to be a power of 2 above
1625+
unsafe { align_offset(self, align) }
16081626
}
1609-
1610-
// SAFETY:
1611-
// It is permissible for `align_offset` to always return `usize::MAX`,
1612-
// algorithm correctness can not depend on `align_offset` returning non-max values.
1613-
//
1614-
// As such the behaviour can't change after replacing `align_offset` with `usize::MAX`, only performance can.
1615-
unsafe { intrinsics::const_eval_select((self, align), ctfe_impl, rt_impl) }
16161627
}
16171628

16181629
/// Returns whether the pointer is properly aligned for `T`.
1630+
///
1631+
/// # Examples
1632+
///
1633+
/// Basic usage:
1634+
/// ```
1635+
/// #![feature(pointer_is_aligned)]
1636+
/// #![feature(pointer_byte_offsets)]
1637+
///
1638+
/// // On some platforms, the alignment of i32 is less than 4.
1639+
/// #[repr(align(4))]
1640+
/// struct AlignedI32(i32);
1641+
///
1642+
/// let mut data = AlignedI32(42);
1643+
/// let ptr = &mut data as *mut AlignedI32;
1644+
///
1645+
/// assert!(ptr.is_aligned());
1646+
/// assert!(!ptr.wrapping_byte_add(1).is_aligned());
1647+
/// ```
1648+
///
1649+
/// # At compiletime
1650+
/// **Note: Alignment at compiletime is experimental and subject to change. See the
1651+
/// [tracking issue] for details.**
1652+
///
1653+
/// At compiletime, the compiler may not know where a value will end up in memory.
1654+
/// Calling this function on a pointer created from a reference at compiletime will only
1655+
/// return `true` if the pointer is guaranteed to be aligned. This means that the pointer
1656+
/// is never aligned if cast to a type with a stricter alignment than the reference's
1657+
/// underlying allocation.
1658+
///
1659+
#[cfg_attr(bootstrap, doc = "```ignore")]
1660+
#[cfg_attr(not(bootstrap), doc = "```")]
1661+
/// #![feature(pointer_is_aligned)]
1662+
/// #![feature(const_pointer_is_aligned)]
1663+
/// #![feature(const_mut_refs)]
1664+
///
1665+
/// // On some platforms, the alignment of primitives is less than their size.
1666+
/// #[repr(align(4))]
1667+
/// struct AlignedI32(i32);
1668+
/// #[repr(align(8))]
1669+
/// struct AlignedI64(i64);
1670+
///
1671+
/// const _: () = {
1672+
/// let mut data = AlignedI32(42);
1673+
/// let ptr = &mut data as *mut AlignedI32;
1674+
/// assert!(ptr.is_aligned());
1675+
///
1676+
/// // At runtime either `ptr1` or `ptr2` would be aligned, but at compiletime neither is aligned.
1677+
/// let ptr1 = ptr.cast::<AlignedI64>();
1678+
/// let ptr2 = ptr.wrapping_add(1).cast::<AlignedI64>();
1679+
/// assert!(!ptr1.is_aligned());
1680+
/// assert!(!ptr2.is_aligned());
1681+
/// };
1682+
/// ```
1683+
///
1684+
/// Due to this behavior, it is possible that a runtime pointer derived from a compiletime
1685+
/// pointer is aligned, even if the compiletime pointer wasn't aligned.
1686+
///
1687+
#[cfg_attr(bootstrap, doc = "```ignore")]
1688+
#[cfg_attr(not(bootstrap), doc = "```")]
1689+
/// #![feature(pointer_is_aligned)]
1690+
/// #![feature(const_pointer_is_aligned)]
1691+
///
1692+
/// // On some platforms, the alignment of primitives is less than their size.
1693+
/// #[repr(align(4))]
1694+
/// struct AlignedI32(i32);
1695+
/// #[repr(align(8))]
1696+
/// struct AlignedI64(i64);
1697+
///
1698+
/// // At compiletime, neither `COMPTIME_PTR` nor `COMPTIME_PTR + 1` is aligned.
1699+
/// // Also, note that mutable references are not allowed in the final value of constants.
1700+
/// const COMPTIME_PTR: *mut AlignedI32 = (&AlignedI32(42) as *const AlignedI32).cast_mut();
1701+
/// const _: () = assert!(!COMPTIME_PTR.cast::<AlignedI64>().is_aligned());
1702+
/// const _: () = assert!(!COMPTIME_PTR.wrapping_add(1).cast::<AlignedI64>().is_aligned());
1703+
///
1704+
/// // At runtime, either `runtime_ptr` or `runtime_ptr + 1` is aligned.
1705+
/// let runtime_ptr = COMPTIME_PTR;
1706+
/// assert_ne!(
1707+
/// runtime_ptr.cast::<AlignedI64>().is_aligned(),
1708+
/// runtime_ptr.wrapping_add(1).cast::<AlignedI64>().is_aligned(),
1709+
/// );
1710+
/// ```
1711+
///
1712+
/// If a pointer is created from a fixed address, this function behaves the same during
1713+
/// runtime and compiletime.
1714+
///
1715+
#[cfg_attr(bootstrap, doc = "```ignore")]
1716+
#[cfg_attr(not(bootstrap), doc = "```")]
1717+
/// #![feature(pointer_is_aligned)]
1718+
/// #![feature(const_pointer_is_aligned)]
1719+
///
1720+
/// // On some platforms, the alignment of primitives is less than their size.
1721+
/// #[repr(align(4))]
1722+
/// struct AlignedI32(i32);
1723+
/// #[repr(align(8))]
1724+
/// struct AlignedI64(i64);
1725+
///
1726+
/// const _: () = {
1727+
/// let ptr = 40 as *mut AlignedI32;
1728+
/// assert!(ptr.is_aligned());
1729+
///
1730+
/// // For pointers with a known address, runtime and compiletime behavior are identical.
1731+
/// let ptr1 = ptr.cast::<AlignedI64>();
1732+
/// let ptr2 = ptr.wrapping_add(1).cast::<AlignedI64>();
1733+
/// assert!(ptr1.is_aligned());
1734+
/// assert!(!ptr2.is_aligned());
1735+
/// };
1736+
/// ```
1737+
///
1738+
/// [tracking issue]: https://github.com/rust-lang/rust/issues/104203
16191739
#[must_use]
16201740
#[inline]
16211741
#[unstable(feature = "pointer_is_aligned", issue = "96284")]
1622-
pub fn is_aligned(self) -> bool
1742+
#[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "104203")]
1743+
pub const fn is_aligned(self) -> bool
16231744
where
16241745
T: Sized,
16251746
{
1626-
self.is_aligned_to(core::mem::align_of::<T>())
1747+
self.is_aligned_to(mem::align_of::<T>())
16271748
}
16281749

16291750
/// Returns whether the pointer is aligned to `align`.
@@ -1634,16 +1755,123 @@ impl<T: ?Sized> *mut T {
16341755
/// # Panics
16351756
///
16361757
/// The function panics if `align` is not a power-of-two (this includes 0).
1758+
///
1759+
/// # Examples
1760+
///
1761+
/// Basic usage:
1762+
/// ```
1763+
/// #![feature(pointer_is_aligned)]
1764+
/// #![feature(pointer_byte_offsets)]
1765+
///
1766+
/// // On some platforms, the alignment of i32 is less than 4.
1767+
/// #[repr(align(4))]
1768+
/// struct AlignedI32(i32);
1769+
///
1770+
/// let mut data = AlignedI32(42);
1771+
/// let ptr = &mut data as *mut AlignedI32;
1772+
///
1773+
/// assert!(ptr.is_aligned_to(1));
1774+
/// assert!(ptr.is_aligned_to(2));
1775+
/// assert!(ptr.is_aligned_to(4));
1776+
///
1777+
/// assert!(ptr.wrapping_byte_add(2).is_aligned_to(2));
1778+
/// assert!(!ptr.wrapping_byte_add(2).is_aligned_to(4));
1779+
///
1780+
/// assert_ne!(ptr.is_aligned_to(8), ptr.wrapping_add(1).is_aligned_to(8));
1781+
/// ```
1782+
///
1783+
/// # At compiletime
1784+
/// **Note: Alignment at compiletime is experimental and subject to change. See the
1785+
/// [tracking issue] for details.**
1786+
///
1787+
/// At compiletime, the compiler may not know where a value will end up in memory.
1788+
/// Calling this function on a pointer created from a reference at compiletime will only
1789+
/// return `true` if the pointer is guaranteed to be aligned. This means that the pointer
1790+
/// cannot be stricter aligned than the reference's underlying allocation.
1791+
///
1792+
#[cfg_attr(bootstrap, doc = "```ignore")]
1793+
#[cfg_attr(not(bootstrap), doc = "```")]
1794+
/// #![feature(pointer_is_aligned)]
1795+
/// #![feature(const_pointer_is_aligned)]
1796+
/// #![feature(const_mut_refs)]
1797+
///
1798+
/// // On some platforms, the alignment of i32 is less than 4.
1799+
/// #[repr(align(4))]
1800+
/// struct AlignedI32(i32);
1801+
///
1802+
/// const _: () = {
1803+
/// let mut data = AlignedI32(42);
1804+
/// let ptr = &mut data as *mut AlignedI32;
1805+
///
1806+
/// assert!(ptr.is_aligned_to(1));
1807+
/// assert!(ptr.is_aligned_to(2));
1808+
/// assert!(ptr.is_aligned_to(4));
1809+
///
1810+
/// // At compiletime, we know for sure that the pointer isn't aligned to 8.
1811+
/// assert!(!ptr.is_aligned_to(8));
1812+
/// assert!(!ptr.wrapping_add(1).is_aligned_to(8));
1813+
/// };
1814+
/// ```
1815+
///
1816+
/// Due to this behavior, it is possible that a runtime pointer derived from a compiletime
1817+
/// pointer is aligned, even if the compiletime pointer wasn't aligned.
1818+
///
1819+
#[cfg_attr(bootstrap, doc = "```ignore")]
1820+
#[cfg_attr(not(bootstrap), doc = "```")]
1821+
/// #![feature(pointer_is_aligned)]
1822+
/// #![feature(const_pointer_is_aligned)]
1823+
///
1824+
/// // On some platforms, the alignment of i32 is less than 4.
1825+
/// #[repr(align(4))]
1826+
/// struct AlignedI32(i32);
1827+
///
1828+
/// // At compiletime, neither `COMPTIME_PTR` nor `COMPTIME_PTR + 1` is aligned.
1829+
/// // Also, note that mutable references are not allowed in the final value of constants.
1830+
/// const COMPTIME_PTR: *mut AlignedI32 = (&AlignedI32(42) as *const AlignedI32).cast_mut();
1831+
/// const _: () = assert!(!COMPTIME_PTR.is_aligned_to(8));
1832+
/// const _: () = assert!(!COMPTIME_PTR.wrapping_add(1).is_aligned_to(8));
1833+
///
1834+
/// // At runtime, either `runtime_ptr` or `runtime_ptr + 1` is aligned.
1835+
/// let runtime_ptr = COMPTIME_PTR;
1836+
/// assert_ne!(
1837+
/// runtime_ptr.is_aligned_to(8),
1838+
/// runtime_ptr.wrapping_add(1).is_aligned_to(8),
1839+
/// );
1840+
/// ```
1841+
///
1842+
/// If a pointer is created from a fixed address, this function behaves the same during
1843+
/// runtime and compiletime.
1844+
///
1845+
#[cfg_attr(bootstrap, doc = "```ignore")]
1846+
#[cfg_attr(not(bootstrap), doc = "```")]
1847+
/// #![feature(pointer_is_aligned)]
1848+
/// #![feature(const_pointer_is_aligned)]
1849+
///
1850+
/// const _: () = {
1851+
/// let ptr = 40 as *mut u8;
1852+
/// assert!(ptr.is_aligned_to(1));
1853+
/// assert!(ptr.is_aligned_to(2));
1854+
/// assert!(ptr.is_aligned_to(4));
1855+
/// assert!(ptr.is_aligned_to(8));
1856+
/// assert!(!ptr.is_aligned_to(16));
1857+
/// };
1858+
/// ```
1859+
///
1860+
/// [tracking issue]: https://github.com/rust-lang/rust/issues/104203
16371861
#[must_use]
16381862
#[inline]
16391863
#[unstable(feature = "pointer_is_aligned", issue = "96284")]
1640-
pub fn is_aligned_to(self, align: usize) -> bool {
1864+
#[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "104203")]
1865+
pub const fn is_aligned_to(self, align: usize) -> bool {
16411866
if !align.is_power_of_two() {
16421867
panic!("is_aligned_to: align is not a power-of-two");
16431868
}
16441869

1645-
// Cast is needed for `T: !Sized`
1646-
self.cast::<u8>().addr() & align - 1 == 0
1870+
// We can't use the address of `self` in a `const fn`, so we use `align_offset` instead.
1871+
// The cast to `()` is used to
1872+
// 1. deal with fat pointers; and
1873+
// 2. ensure that `align_offset` doesn't actually try to compute an offset.
1874+
self.cast::<()>().align_offset(align) == 0
16471875
}
16481876
}
16491877

‎library/core/tests/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![feature(array_windows)]
55
#![feature(bigint_helper_methods)]
66
#![feature(cell_update)]
7+
#![feature(const_align_offset)]
78
#![feature(const_assume)]
89
#![feature(const_align_of_val_raw)]
910
#![feature(const_black_box)]
@@ -18,6 +19,7 @@
1819
#![feature(const_nonnull_new)]
1920
#![feature(const_num_from_num)]
2021
#![feature(const_pointer_byte_offsets)]
22+
#![feature(const_pointer_is_aligned)]
2123
#![feature(const_ptr_as_ref)]
2224
#![feature(const_ptr_read)]
2325
#![feature(const_ptr_write)]
@@ -81,6 +83,7 @@
8183
#![feature(never_type)]
8284
#![feature(unwrap_infallible)]
8385
#![feature(pointer_byte_offsets)]
86+
#![feature(pointer_is_aligned)]
8487
#![feature(portable_simd)]
8588
#![feature(ptr_metadata)]
8689
#![feature(once_cell)]

‎library/core/tests/ptr.rs

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,23 @@ fn align_offset_zst() {
358358
}
359359
}
360360

361+
#[test]
362+
#[cfg(not(bootstrap))]
363+
fn align_offset_zst_const() {
364+
const {
365+
// For pointers of stride = 0, the pointer is already aligned or it cannot be aligned at
366+
// all, because no amount of elements will align the pointer.
367+
let mut p = 1;
368+
while p < 1024 {
369+
assert!(ptr::invalid::<()>(p).align_offset(p) == 0);
370+
if p != 1 {
371+
assert!(ptr::invalid::<()>(p + 1).align_offset(p) == !0);
372+
}
373+
p = (p + 1).next_power_of_two();
374+
}
375+
}
376+
}
377+
361378
#[test]
362379
fn align_offset_stride_one() {
363380
// For pointers of stride = 1, the pointer can always be aligned. The offset is equal to
@@ -379,6 +396,26 @@ fn align_offset_stride_one() {
379396
}
380397
}
381398

399+
#[test]
400+
#[cfg(not(bootstrap))]
401+
fn align_offset_stride_one_const() {
402+
const {
403+
// For pointers of stride = 1, the pointer can always be aligned. The offset is equal to
404+
// number of bytes.
405+
let mut align = 1;
406+
while align < 1024 {
407+
let mut ptr = 1;
408+
while ptr < 2 * align {
409+
let expected = ptr % align;
410+
let offset = if expected == 0 { 0 } else { align - expected };
411+
assert!(ptr::invalid::<u8>(ptr).align_offset(align) == offset);
412+
ptr += 1;
413+
}
414+
align = (align + 1).next_power_of_two();
415+
}
416+
}
417+
}
418+
382419
#[test]
383420
fn align_offset_various_strides() {
384421
unsafe fn test_stride<T>(ptr: *const T, align: usize) -> bool {
@@ -455,6 +492,182 @@ fn align_offset_various_strides() {
455492
assert!(!x);
456493
}
457494

495+
#[test]
496+
#[cfg(not(bootstrap))]
497+
fn align_offset_various_strides_const() {
498+
const unsafe fn test_stride<T>(ptr: *const T, numptr: usize, align: usize) {
499+
let mut expected = usize::MAX;
500+
// Naive but definitely correct way to find the *first* aligned element of stride::<T>.
501+
let mut el = 0;
502+
while el < align {
503+
if (numptr + el * ::std::mem::size_of::<T>()) % align == 0 {
504+
expected = el;
505+
break;
506+
}
507+
el += 1;
508+
}
509+
let got = ptr.align_offset(align);
510+
assert!(got == expected);
511+
}
512+
513+
const {
514+
// For pointers of stride != 1, we verify the algorithm against the naivest possible
515+
// implementation
516+
let mut align = 1;
517+
let limit = 32;
518+
while align < limit {
519+
let mut ptr = 1;
520+
while ptr < 4 * align {
521+
unsafe {
522+
#[repr(packed)]
523+
struct A3(u16, u8);
524+
test_stride::<A3>(ptr::invalid::<A3>(ptr), ptr, align);
525+
526+
struct A4(u32);
527+
test_stride::<A4>(ptr::invalid::<A4>(ptr), ptr, align);
528+
529+
#[repr(packed)]
530+
struct A5(u32, u8);
531+
test_stride::<A5>(ptr::invalid::<A5>(ptr), ptr, align);
532+
533+
#[repr(packed)]
534+
struct A6(u32, u16);
535+
test_stride::<A6>(ptr::invalid::<A6>(ptr), ptr, align);
536+
537+
#[repr(packed)]
538+
struct A7(u32, u16, u8);
539+
test_stride::<A7>(ptr::invalid::<A7>(ptr), ptr, align);
540+
541+
#[repr(packed)]
542+
struct A8(u32, u32);
543+
test_stride::<A8>(ptr::invalid::<A8>(ptr), ptr, align);
544+
545+
#[repr(packed)]
546+
struct A9(u32, u32, u8);
547+
test_stride::<A9>(ptr::invalid::<A9>(ptr), ptr, align);
548+
549+
#[repr(packed)]
550+
struct A10(u32, u32, u16);
551+
test_stride::<A10>(ptr::invalid::<A10>(ptr), ptr, align);
552+
553+
test_stride::<u32>(ptr::invalid::<u32>(ptr), ptr, align);
554+
test_stride::<u128>(ptr::invalid::<u128>(ptr), ptr, align);
555+
}
556+
ptr += 1;
557+
}
558+
align = (align + 1).next_power_of_two();
559+
}
560+
}
561+
}
562+
563+
#[test]
564+
#[cfg(not(bootstrap))]
565+
fn align_offset_with_provenance_const() {
566+
const {
567+
// On some platforms (e.g. msp430-none-elf), the alignment of `i32` is less than 4.
568+
#[repr(align(4))]
569+
struct AlignedI32(i32);
570+
571+
let data = AlignedI32(42);
572+
573+
// `stride % align == 0` (usual case)
574+
575+
let ptr: *const i32 = &data.0;
576+
assert!(ptr.align_offset(1) == 0);
577+
assert!(ptr.align_offset(2) == 0);
578+
assert!(ptr.align_offset(4) == 0);
579+
assert!(ptr.align_offset(8) == usize::MAX);
580+
assert!(ptr.wrapping_byte_add(1).align_offset(1) == 0);
581+
assert!(ptr.wrapping_byte_add(1).align_offset(2) == usize::MAX);
582+
assert!(ptr.wrapping_byte_add(2).align_offset(1) == 0);
583+
assert!(ptr.wrapping_byte_add(2).align_offset(2) == 0);
584+
assert!(ptr.wrapping_byte_add(2).align_offset(4) == usize::MAX);
585+
assert!(ptr.wrapping_byte_add(3).align_offset(1) == 0);
586+
assert!(ptr.wrapping_byte_add(3).align_offset(2) == usize::MAX);
587+
588+
assert!(ptr.wrapping_add(42).align_offset(4) == 0);
589+
assert!(ptr.wrapping_add(42).align_offset(8) == usize::MAX);
590+
591+
let ptr1: *const i8 = ptr.cast();
592+
assert!(ptr1.align_offset(1) == 0);
593+
assert!(ptr1.align_offset(2) == 0);
594+
assert!(ptr1.align_offset(4) == 0);
595+
assert!(ptr1.align_offset(8) == usize::MAX);
596+
assert!(ptr1.wrapping_byte_add(1).align_offset(1) == 0);
597+
assert!(ptr1.wrapping_byte_add(1).align_offset(2) == 1);
598+
assert!(ptr1.wrapping_byte_add(1).align_offset(4) == 3);
599+
assert!(ptr1.wrapping_byte_add(1).align_offset(8) == usize::MAX);
600+
assert!(ptr1.wrapping_byte_add(2).align_offset(1) == 0);
601+
assert!(ptr1.wrapping_byte_add(2).align_offset(2) == 0);
602+
assert!(ptr1.wrapping_byte_add(2).align_offset(4) == 2);
603+
assert!(ptr1.wrapping_byte_add(2).align_offset(8) == usize::MAX);
604+
assert!(ptr1.wrapping_byte_add(3).align_offset(1) == 0);
605+
assert!(ptr1.wrapping_byte_add(3).align_offset(2) == 1);
606+
assert!(ptr1.wrapping_byte_add(3).align_offset(4) == 1);
607+
assert!(ptr1.wrapping_byte_add(3).align_offset(8) == usize::MAX);
608+
609+
let ptr2: *const i16 = ptr.cast();
610+
assert!(ptr2.align_offset(1) == 0);
611+
assert!(ptr2.align_offset(2) == 0);
612+
assert!(ptr2.align_offset(4) == 0);
613+
assert!(ptr2.align_offset(8) == usize::MAX);
614+
assert!(ptr2.wrapping_byte_add(1).align_offset(1) == 0);
615+
assert!(ptr2.wrapping_byte_add(1).align_offset(2) == usize::MAX);
616+
assert!(ptr2.wrapping_byte_add(2).align_offset(1) == 0);
617+
assert!(ptr2.wrapping_byte_add(2).align_offset(2) == 0);
618+
assert!(ptr2.wrapping_byte_add(2).align_offset(4) == 1);
619+
assert!(ptr2.wrapping_byte_add(2).align_offset(8) == usize::MAX);
620+
assert!(ptr2.wrapping_byte_add(3).align_offset(1) == 0);
621+
assert!(ptr2.wrapping_byte_add(3).align_offset(2) == usize::MAX);
622+
623+
let ptr3: *const i64 = ptr.cast();
624+
assert!(ptr3.align_offset(1) == 0);
625+
assert!(ptr3.align_offset(2) == 0);
626+
assert!(ptr3.align_offset(4) == 0);
627+
assert!(ptr3.align_offset(8) == usize::MAX);
628+
assert!(ptr3.wrapping_byte_add(1).align_offset(1) == 0);
629+
assert!(ptr3.wrapping_byte_add(1).align_offset(2) == usize::MAX);
630+
631+
// `stride % align != 0` (edge case)
632+
633+
let ptr4: *const [u8; 3] = ptr.cast();
634+
assert!(ptr4.align_offset(1) == 0);
635+
assert!(ptr4.align_offset(2) == 0);
636+
assert!(ptr4.align_offset(4) == 0);
637+
assert!(ptr4.align_offset(8) == usize::MAX);
638+
assert!(ptr4.wrapping_byte_add(1).align_offset(1) == 0);
639+
assert!(ptr4.wrapping_byte_add(1).align_offset(2) == 1);
640+
assert!(ptr4.wrapping_byte_add(1).align_offset(4) == 1);
641+
assert!(ptr4.wrapping_byte_add(1).align_offset(8) == usize::MAX);
642+
assert!(ptr4.wrapping_byte_add(2).align_offset(1) == 0);
643+
assert!(ptr4.wrapping_byte_add(2).align_offset(2) == 0);
644+
assert!(ptr4.wrapping_byte_add(2).align_offset(4) == 2);
645+
assert!(ptr4.wrapping_byte_add(2).align_offset(8) == usize::MAX);
646+
assert!(ptr4.wrapping_byte_add(3).align_offset(1) == 0);
647+
assert!(ptr4.wrapping_byte_add(3).align_offset(2) == 1);
648+
assert!(ptr4.wrapping_byte_add(3).align_offset(4) == 3);
649+
assert!(ptr4.wrapping_byte_add(3).align_offset(8) == usize::MAX);
650+
651+
let ptr5: *const [u8; 5] = ptr.cast();
652+
assert!(ptr5.align_offset(1) == 0);
653+
assert!(ptr5.align_offset(2) == 0);
654+
assert!(ptr5.align_offset(4) == 0);
655+
assert!(ptr5.align_offset(8) == usize::MAX);
656+
assert!(ptr5.wrapping_byte_add(1).align_offset(1) == 0);
657+
assert!(ptr5.wrapping_byte_add(1).align_offset(2) == 1);
658+
assert!(ptr5.wrapping_byte_add(1).align_offset(4) == 3);
659+
assert!(ptr5.wrapping_byte_add(1).align_offset(8) == usize::MAX);
660+
assert!(ptr5.wrapping_byte_add(2).align_offset(1) == 0);
661+
assert!(ptr5.wrapping_byte_add(2).align_offset(2) == 0);
662+
assert!(ptr5.wrapping_byte_add(2).align_offset(4) == 2);
663+
assert!(ptr5.wrapping_byte_add(2).align_offset(8) == usize::MAX);
664+
assert!(ptr5.wrapping_byte_add(3).align_offset(1) == 0);
665+
assert!(ptr5.wrapping_byte_add(3).align_offset(2) == 1);
666+
assert!(ptr5.wrapping_byte_add(3).align_offset(4) == 1);
667+
assert!(ptr5.wrapping_byte_add(3).align_offset(8) == usize::MAX);
668+
}
669+
}
670+
458671
#[test]
459672
fn align_offset_issue_103361() {
460673
#[cfg(target_pointer_width = "64")]
@@ -467,6 +680,72 @@ fn align_offset_issue_103361() {
467680
let _ = (SIZE as *const HugeSize).align_offset(SIZE);
468681
}
469682

683+
#[test]
684+
#[cfg(not(bootstrap))]
685+
fn align_offset_issue_103361_const() {
686+
#[cfg(target_pointer_width = "64")]
687+
const SIZE: usize = 1 << 47;
688+
#[cfg(target_pointer_width = "32")]
689+
const SIZE: usize = 1 << 30;
690+
#[cfg(target_pointer_width = "16")]
691+
const SIZE: usize = 1 << 13;
692+
struct HugeSize([u8; SIZE - 1]);
693+
694+
const {
695+
assert!(ptr::invalid::<HugeSize>(SIZE - 1).align_offset(SIZE) == SIZE - 1);
696+
assert!(ptr::invalid::<HugeSize>(SIZE).align_offset(SIZE) == 0);
697+
assert!(ptr::invalid::<HugeSize>(SIZE + 1).align_offset(SIZE) == 1);
698+
}
699+
}
700+
701+
#[test]
702+
fn is_aligned() {
703+
let data = 42;
704+
let ptr: *const i32 = &data;
705+
assert!(ptr.is_aligned());
706+
assert!(ptr.is_aligned_to(1));
707+
assert!(ptr.is_aligned_to(2));
708+
assert!(ptr.is_aligned_to(4));
709+
assert!(ptr.wrapping_byte_add(2).is_aligned_to(1));
710+
assert!(ptr.wrapping_byte_add(2).is_aligned_to(2));
711+
assert!(!ptr.wrapping_byte_add(2).is_aligned_to(4));
712+
713+
// At runtime either `ptr` or `ptr+1` is aligned to 8.
714+
assert_ne!(ptr.is_aligned_to(8), ptr.wrapping_add(1).is_aligned_to(8));
715+
}
716+
717+
#[test]
718+
#[cfg(not(bootstrap))]
719+
fn is_aligned_const() {
720+
const {
721+
let data = 42;
722+
let ptr: *const i32 = &data;
723+
assert!(ptr.is_aligned());
724+
assert!(ptr.is_aligned_to(1));
725+
assert!(ptr.is_aligned_to(2));
726+
assert!(ptr.is_aligned_to(4));
727+
assert!(ptr.wrapping_byte_add(2).is_aligned_to(1));
728+
assert!(ptr.wrapping_byte_add(2).is_aligned_to(2));
729+
assert!(!ptr.wrapping_byte_add(2).is_aligned_to(4));
730+
731+
// At comptime neither `ptr` nor `ptr+1` is aligned to 8.
732+
assert!(!ptr.is_aligned_to(8));
733+
assert!(!ptr.wrapping_add(1).is_aligned_to(8));
734+
}
735+
}
736+
737+
#[test]
738+
#[cfg(bootstrap)]
739+
fn is_aligned_const() {
740+
const {
741+
let data = 42;
742+
let ptr: *const i32 = &data;
743+
// The bootstrap compiler always returns false for is_aligned.
744+
assert!(!ptr.is_aligned());
745+
assert!(!ptr.is_aligned_to(1));
746+
}
747+
}
748+
470749
#[test]
471750
fn offset_from() {
472751
let mut a = [0; 5];

‎src/test/assembly/is_aligned.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// assembly-output: emit-asm
2+
// min-llvm-version: 14.0
3+
// only-x86_64
4+
// revisions: opt-speed opt-size
5+
// [opt-speed] compile-flags: -Copt-level=1
6+
// [opt-size] compile-flags: -Copt-level=s
7+
#![crate_type="rlib"]
8+
9+
#![feature(core_intrinsics)]
10+
#![feature(pointer_is_aligned)]
11+
12+
// CHECK-LABEL: is_aligned_to_unchecked
13+
// CHECK: decq
14+
// CHECK-NEXT: testq
15+
// CHECK-NEXT: sete
16+
// CHECK: retq
17+
#[no_mangle]
18+
pub unsafe fn is_aligned_to_unchecked(ptr: *const u8, align: usize) -> bool {
19+
unsafe {
20+
std::intrinsics::assume(align.is_power_of_two())
21+
}
22+
ptr.is_aligned_to(align)
23+
}
24+
25+
// CHECK-LABEL: is_aligned_1
26+
// CHECK: movb $1
27+
// CHECK: retq
28+
#[no_mangle]
29+
pub fn is_aligned_1(ptr: *const u8) -> bool {
30+
ptr.is_aligned()
31+
}
32+
33+
// CHECK-LABEL: is_aligned_2
34+
// CHECK: testb $1
35+
// CHECK-NEXT: sete
36+
// CHECK: retq
37+
#[no_mangle]
38+
pub fn is_aligned_2(ptr: *const u16) -> bool {
39+
ptr.is_aligned()
40+
}
41+
42+
// CHECK-LABEL: is_aligned_4
43+
// CHECK: testb $3
44+
// CHECK-NEXT: sete
45+
// CHECK: retq
46+
#[no_mangle]
47+
pub fn is_aligned_4(ptr: *const u32) -> bool {
48+
ptr.is_aligned()
49+
}
50+
51+
// CHECK-LABEL: is_aligned_8
52+
// CHECK: testb $7
53+
// CHECK-NEXT: sete
54+
// CHECK: retq
55+
#[no_mangle]
56+
pub fn is_aligned_8(ptr: *const u64) -> bool {
57+
ptr.is_aligned()
58+
}

‎src/tools/miri/src/shims/intrinsics/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
368368
}
369369

370370
// Other
371-
"exact_div" => {
372-
let [num, denom] = check_arg_count(args)?;
373-
this.exact_div(&this.read_immediate(num)?, &this.read_immediate(denom)?, dest)?;
374-
}
375-
376371
"breakpoint" => {
377372
let [] = check_arg_count(args)?;
378373
// normally this would raise a SIGTRAP, which aborts if no debugger is connected

0 commit comments

Comments
 (0)
Please sign in to comment.