Skip to content

Constify pointer::is_aligned{,to} #102753

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

Closed
wants to merge 7 commits into from
Closed
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
187 changes: 134 additions & 53 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use rustc_hir::def::DefKind;
use rustc_middle::mir;
use rustc_middle::mir::interpret::PointerArithmetic;
use rustc_middle::ty::layout::FnAbiOf;
use rustc_middle::ty::{self, Ty, TyCtxt};
use std::borrow::Borrow;
use std::collections::hash_map::Entry;
use std::hash::Hash;
use std::ops::ControlFlow;

use rustc_data_structures::fx::FxHashMap;
use std::fmt;
Expand All @@ -17,58 +20,12 @@ use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi as CallAbi;

use crate::interpret::{
self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
InterpResult, OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
};

use super::error::*;

impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> {
/// "Intercept" a function call to a panic-related function
/// because we have something special to do for it.
/// If this returns successfully (`Ok`), the function should just be evaluated normally.
fn hook_special_const_fn(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
// All `#[rustc_do_not_const_check]` functions should be hooked here.
let def_id = instance.def_id();

if Some(def_id) == self.tcx.lang_items().panic_display()
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
{
// &str or &&str
assert!(args.len() == 1);

let mut msg_place = self.deref_operand(&args[0])?;
while msg_place.layout.ty.is_ref() {
msg_place = self.deref_operand(&msg_place.into())?;
}

let msg = Symbol::intern(self.read_str(&msg_place)?);
let span = self.find_closest_untracked_caller_location();
let (file, line, col) = self.location_triple_for_span(span);
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
// For panic_fmt, call const_panic_fmt instead.
if let Some(const_panic_fmt) = self.tcx.lang_items().const_panic_fmt() {
return Ok(Some(
ty::Instance::resolve(
*self.tcx,
ty::ParamEnv::reveal_all(),
const_panic_fmt,
self.tcx.intern_substs(&[]),
)
.unwrap()
.unwrap(),
));
}
}
Ok(None)
}
}

/// Extra machine state for CTFE, and the Machine instance
pub struct CompileTimeInterpreter<'mir, 'tcx> {
/// For now, the number of terminators that can be evaluated before we throw a resource
Expand Down Expand Up @@ -191,6 +148,125 @@ impl interpret::MayLeak for ! {
}

impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
/// "Intercept" a function call, because we have something special to do for it.
/// All `#[rustc_do_not_const_check]` functions should be hooked here.
/// If this returns `Some` function, which may be `instance` or a different function with
/// compatible arguments, then evaluation should continue with that function.
/// If this returns `None`, the function call has been handled and the function has returned.
fn hook_special_const_fn(
&mut self,
instance: ty::Instance<'tcx>,
_abi: CallAbi,
args: &[OpTy<'tcx>],
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
let def_id = instance.def_id();

if Some(def_id) == self.tcx.lang_items().panic_display()
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
{
// &str or &&str
assert!(args.len() == 1);

let mut msg_place = self.deref_operand(&args[0])?;
while msg_place.layout.ty.is_ref() {
msg_place = self.deref_operand(&msg_place.into())?;
}

let msg = Symbol::intern(self.read_str(&msg_place)?);
let span = self.find_closest_untracked_caller_location();
let (file, line, col) = self.location_triple_for_span(span);
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
// For panic_fmt, call const_panic_fmt instead.
let Some(const_def_id) = self.tcx.lang_items().const_panic_fmt() else {
bug!("`const_panic_fmt` must be defined to call `panic_fmt` in const eval")
};
let new_instance = ty::Instance::resolve(
*self.tcx,
ty::ParamEnv::reveal_all(),
const_def_id,
instance.substs,
)
.unwrap()
.unwrap();

return Ok(Some(new_instance));
} else if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
// For align_offset, we replace the function call if the pointer has no address.
match self.align_offset(instance, args, dest, ret)? {
ControlFlow::Continue(()) => return Ok(Some(instance)),
ControlFlow::Break(()) => return Ok(None),
}
}
Ok(Some(instance))
}

/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
/// may not have an address.
///
/// If `ptr` does have a known address, then we return `CONTINUE` and the function call should
/// proceed as normal.
///
/// If `ptr` doesn't have an address, but its underlying allocation's alignment is at most
/// `target_align`, then we call the function again with an dummy address relative to the
/// allocation.
///
/// If `ptr` doesn't have an address and `target_align` is stricter than the underlying
/// allocation's alignment, then we return `usize::MAX` immediately.
fn align_offset(
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx, ControlFlow<()>> {
assert_eq!(args.len(), 2);

let ptr = self.read_pointer(&args[0])?;
let target_align = self.read_scalar(&args[1])?.to_machine_usize(self)?;

if !target_align.is_power_of_two() {
throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
}

match self.ptr_try_get_alloc_id(ptr) {
Ok((alloc_id, offset, _extra)) => {
let (_size, alloc_align, _kind) = self.get_alloc_info(alloc_id);

if target_align <= alloc_align.bytes() {
// Extract the address relative to the allocation base that is definitely
// sufficiently aligned and call `align_offset` again.
let addr = ImmTy::from_uint(offset.bytes(), args[0].layout).into();
let align = ImmTy::from_uint(target_align, args[1].layout).into();

let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
self.eval_fn_call(
FnVal::Instance(instance),
(CallAbi::Rust, fn_abi),
&[addr, align],
false,
dest,
ret,
StackPopUnwind::NotAllowed,
)?;
Ok(ControlFlow::BREAK)
} else {
// Not alignable in const, return `usize::MAX`.
let usize_max = Scalar::from_machine_usize(self.machine_usize_max(), self);
self.write_scalar(usize_max, dest)?;
self.return_to_block(ret)?;
Ok(ControlFlow::BREAK)
}
}
Err(_addr) => {
// The pointer has an address, continue with function call.
Ok(ControlFlow::CONTINUE)
}
}
}

/// See documentation on the `ptr_guaranteed_cmp` intrinsic.
fn guaranteed_cmp(&mut self, a: Scalar, b: Scalar) -> InterpResult<'tcx, u8> {
Ok(match (a, b) {
Expand Down Expand Up @@ -271,8 +347,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
instance: ty::Instance<'tcx>,
_abi: CallAbi,
args: &[OpTy<'tcx>],
_dest: &PlaceTy<'tcx>,
_ret: Option<mir::BasicBlock>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
_unwind: StackPopUnwind, // unwinding is not supported in consts
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
debug!("find_mir_or_eval_fn: {:?}", instance);
Expand All @@ -291,7 +367,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
}
}

if let Some(new_instance) = ecx.hook_special_const_fn(instance, args)? {
let Some(new_instance) = ecx.hook_special_const_fn(instance, _abi, args, dest, ret)? else {
return Ok(None);
};

if new_instance != instance {
// We call another const fn instead.
// However, we return the *original* instance to make backtraces work out
// (and we hope this does not confuse the FnAbi checks too much).
Expand All @@ -300,13 +380,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
new_instance,
_abi,
args,
_dest,
_ret,
dest,
ret,
_unwind,
)?
.map(|(body, _instance)| (body, instance)));
}
}

// This is a const fn. Call it.
Ok(Some((ecx.load_mir(instance.def, None)?, instance)))
}
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let discr_val = self.read_discriminant(&place.into())?.0;
self.write_scalar(discr_val, dest)?;
}
sym::exact_div => {
let l = self.read_immediate(&args[0])?;
let r = self.read_immediate(&args[1])?;
self.exact_div(&l, &r, dest)?;
}
sym::unchecked_shl
| sym::unchecked_shr
| sym::unchecked_add
Expand Down
1 change: 1 addition & 0 deletions library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1846,6 +1846,7 @@ extern "rust-intrinsic" {
/// `x % y != 0` or `y == 0` or `x == T::MIN && y == -1`
///
/// This intrinsic does not have a stable counterpart.
#[rustc_const_unstable(feature = "const_exact_div", issue = "none")]
pub fn exact_div<T: Copy>(x: T, y: T) -> T;

/// Performs an unchecked division, resulting in undefined behavior
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
#![feature(const_cmp)]
#![feature(const_discriminant)]
#![feature(const_eval_select)]
#![feature(const_exact_div)]
#![feature(const_float_bits_conv)]
#![feature(const_float_classify)]
#![feature(const_fmt_arguments_new)]
Expand All @@ -128,6 +129,7 @@
#![feature(const_option)]
#![feature(const_option_ext)]
#![feature(const_pin)]
#![feature(const_pointer_is_aligned)]
#![feature(const_ptr_sub_ptr)]
#![feature(const_replace)]
#![feature(const_ptr_as_ref)]
Expand Down
41 changes: 35 additions & 6 deletions library/core/src/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,24 @@ impl<T: ?Sized> *const T {
/// }
/// # }
/// ```
#[must_use]
#[stable(feature = "align_offset", since = "1.36.0")]
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
#[cfg(not(bootstrap))]
pub const fn align_offset(self, align: usize) -> usize
where
T: Sized,
{
assert!(align.is_power_of_two(), "align_offset: align is not a power-of-two");

// SAFETY: `align` has been checked to be a power of 2 above
unsafe { align_offset(self, align) }
}

#[stable(feature = "align_offset", since = "1.36.0")]
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
#[allow(missing_docs)]
#[cfg(bootstrap)]
pub const fn align_offset(self, align: usize) -> usize
where
T: Sized,
Expand All @@ -1329,10 +1345,13 @@ impl<T: ?Sized> *const T {
}

/// Returns whether the pointer is properly aligned for `T`.
// #[cfg(not(bootstrap))] -- Calling this function in a const context from the bootstrap
// compiler will always return false.
#[must_use]
#[inline]
#[unstable(feature = "pointer_is_aligned", issue = "96284")]
pub fn is_aligned(self) -> bool
#[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "none")]
pub const fn is_aligned(self) -> bool
where
T: Sized,
{
Expand All @@ -1347,16 +1366,26 @@ impl<T: ?Sized> *const T {
/// # Panics
///
/// The function panics if `align` is not a power-of-two (this includes 0).
// #[cfg(not(bootstrap))] -- Calling this function in a const context from the bootstrap
// compiler will always return false.
#[must_use]
#[inline]
#[unstable(feature = "pointer_is_aligned", issue = "96284")]
pub fn is_aligned_to(self, align: usize) -> bool {
if !align.is_power_of_two() {
panic!("is_aligned_to: align is not a power-of-two");
#[rustc_const_unstable(feature = "const_pointer_is_aligned", issue = "none")]
pub const fn is_aligned_to(self, align: usize) -> bool {
assert!(align.is_power_of_two(), "is_aligned_to: align is not a power-of-two");

#[inline]
fn runtime(ptr: *const u8, align: usize) -> bool {
ptr.addr() & (align - 1) == 0
}

const fn comptime(ptr: *const u8, align: usize) -> bool {
ptr.align_offset(align) == 0
}

// Cast is needed for `T: !Sized`
self.cast::<u8>().addr() & align - 1 == 0
// SAFETY: `ptr.align_offset(align)` returns 0 if and only if the pointer is already aligned.
unsafe { intrinsics::const_eval_select((self.cast::<u8>(), align), comptime, runtime) }
}
}

Expand Down
Loading