Skip to content

Commit d4e692d

Browse files
author
Lukas Markeffsky
committed
make const align_offset useful
1 parent f24fc19 commit d4e692d

File tree

4 files changed

+152
-28
lines changed

4 files changed

+152
-28
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use rustc_hir::def::DefKind;
22
use rustc_middle::mir;
3+
use rustc_middle::mir::interpret::PointerArithmetic;
4+
use rustc_middle::ty::layout::FnAbiOf;
35
use rustc_middle::ty::{self, Ty, TyCtxt};
46
use std::borrow::Borrow;
57
use std::collections::hash_map::Entry;
68
use std::hash::Hash;
9+
use std::ops::ControlFlow;
710

811
use rustc_data_structures::fx::FxHashMap;
912
use std::fmt;
@@ -17,8 +20,8 @@ use rustc_target::abi::{Align, Size};
1720
use rustc_target::spec::abi::Abi as CallAbi;
1821

1922
use crate::interpret::{
20-
self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
21-
OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
23+
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
24+
InterpResult, OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
2225
};
2326

2427
use super::error::*;
@@ -145,15 +148,19 @@ impl interpret::MayLeak for ! {
145148
}
146149

147150
impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
148-
/// "Intercept" a function call to a panic-related function
149-
/// because we have something special to do for it.
150-
/// If this returns successfully (`Ok`), the function should just be evaluated normally.
151+
/// "Intercept" a function call, because we have something special to do for it.
152+
/// All `#[rustc_do_not_const_check]` functions should be hooked here.
153+
/// If this returns `Some` function, which may be `instance` or a different function with
154+
/// compatible arguments, then evaluation should continue with that function.
155+
/// If this returns `None`, the function call has been handled and the function has returned.
151156
fn hook_special_const_fn(
152157
&mut self,
153158
instance: ty::Instance<'tcx>,
159+
_abi: CallAbi,
154160
args: &[OpTy<'tcx>],
161+
dest: &PlaceTy<'tcx>,
162+
ret: Option<mir::BasicBlock>,
155163
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
156-
// All `#[rustc_do_not_const_check]` functions should be hooked here.
157164
let def_id = instance.def_id();
158165

159166
if Some(def_id) == self.tcx.lang_items().panic_display()
@@ -173,20 +180,91 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
173180
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
174181
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
175182
// For panic_fmt, call const_panic_fmt instead.
176-
if let Some(const_panic_fmt) = self.tcx.lang_items().const_panic_fmt() {
177-
return Ok(Some(
178-
ty::Instance::resolve(
179-
*self.tcx,
180-
ty::ParamEnv::reveal_all(),
181-
const_panic_fmt,
182-
self.tcx.intern_substs(&[]),
183-
)
184-
.unwrap()
185-
.unwrap(),
186-
));
183+
let Some(const_def_id) = self.tcx.lang_items().const_panic_fmt() else {
184+
bug!("`const_panic_fmt` must be defined to call `panic_fmt` in const eval")
185+
};
186+
let new_instance = ty::Instance::resolve(
187+
*self.tcx,
188+
ty::ParamEnv::reveal_all(),
189+
const_def_id,
190+
instance.substs,
191+
)
192+
.unwrap()
193+
.unwrap();
194+
195+
return Ok(Some(new_instance));
196+
} else if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
197+
// For align_offset, we replace the function call if the pointer has no address.
198+
match self.align_offset(instance, args, dest, ret)? {
199+
ControlFlow::Continue(()) => return Ok(Some(instance)),
200+
ControlFlow::Break(()) => return Ok(None),
201+
}
202+
}
203+
Ok(Some(instance))
204+
}
205+
206+
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
207+
/// may not have an address.
208+
///
209+
/// If `ptr` does have a known address, then we return `CONTINUE` and the function call should
210+
/// proceed as normal.
211+
///
212+
/// If `ptr` doesn't have an address, but its underlying allocation's alignment is at most
213+
/// `target_align`, then we call the function again with an dummy address relative to the
214+
/// allocation.
215+
///
216+
/// If `ptr` doesn't have an address and `target_align` is stricter than the underlying
217+
/// allocation's alignment, then we return `usize::MAX` immediately.
218+
fn align_offset(
219+
&mut self,
220+
instance: ty::Instance<'tcx>,
221+
args: &[OpTy<'tcx>],
222+
dest: &PlaceTy<'tcx>,
223+
ret: Option<mir::BasicBlock>,
224+
) -> InterpResult<'tcx, ControlFlow<()>> {
225+
assert_eq!(args.len(), 2);
226+
227+
let ptr = self.read_pointer(&args[0])?;
228+
let target_align = self.read_scalar(&args[1])?.to_machine_usize(self)?;
229+
230+
if !target_align.is_power_of_two() {
231+
throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
232+
}
233+
234+
match self.ptr_try_get_alloc_id(ptr) {
235+
Ok((alloc_id, offset, _extra)) => {
236+
let (_size, alloc_align, _kind) = self.get_alloc_info(alloc_id);
237+
238+
if target_align <= alloc_align.bytes() {
239+
// Extract the address relative to the allocation base that is definitely
240+
// sufficiently aligned and call `align_offset` again.
241+
let addr = ImmTy::from_uint(offset.bytes(), args[0].layout).into();
242+
let align = ImmTy::from_uint(target_align, args[1].layout).into();
243+
244+
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
245+
self.eval_fn_call(
246+
FnVal::Instance(instance),
247+
(CallAbi::Rust, fn_abi),
248+
&[addr, align],
249+
false,
250+
dest,
251+
ret,
252+
StackPopUnwind::NotAllowed,
253+
)?;
254+
Ok(ControlFlow::BREAK)
255+
} else {
256+
// Not alignable in const, return `usize::MAX`.
257+
let usize_max = Scalar::from_machine_usize(self.machine_usize_max(), self);
258+
self.write_scalar(usize_max, dest)?;
259+
self.return_to_block(ret)?;
260+
Ok(ControlFlow::BREAK)
261+
}
262+
}
263+
Err(_addr) => {
264+
// The pointer has an address, continue with function call.
265+
Ok(ControlFlow::CONTINUE)
187266
}
188267
}
189-
Ok(None)
190268
}
191269

192270
/// See documentation on the `ptr_guaranteed_cmp` intrinsic.
@@ -269,8 +347,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
269347
instance: ty::Instance<'tcx>,
270348
_abi: CallAbi,
271349
args: &[OpTy<'tcx>],
272-
_dest: &PlaceTy<'tcx>,
273-
_ret: Option<mir::BasicBlock>,
350+
dest: &PlaceTy<'tcx>,
351+
ret: Option<mir::BasicBlock>,
274352
_unwind: StackPopUnwind, // unwinding is not supported in consts
275353
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
276354
debug!("find_mir_or_eval_fn: {:?}", instance);
@@ -289,7 +367,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
289367
}
290368
}
291369

292-
if let Some(new_instance) = ecx.hook_special_const_fn(instance, args)? {
370+
let Some(new_instance) = ecx.hook_special_const_fn(instance, _abi, args, dest, ret)? else {
371+
return Ok(None);
372+
};
373+
374+
if new_instance != instance {
293375
// We call another const fn instead.
294376
// However, we return the *original* instance to make backtraces work out
295377
// (and we hope this does not confuse the FnAbi checks too much).
@@ -298,13 +380,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
298380
new_instance,
299381
_abi,
300382
args,
301-
_dest,
302-
_ret,
383+
dest,
384+
ret,
303385
_unwind,
304386
)?
305387
.map(|(body, _instance)| (body, instance)));
306388
}
307389
}
390+
308391
// This is a const fn. Call it.
309392
Ok(Some((ecx.load_mir(instance.def, None)?, instance)))
310393
}

library/core/src/ptr/const_ptr.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,21 @@ impl<T: ?Sized> *const T {
13031303
/// ```
13041304
#[stable(feature = "align_offset", since = "1.36.0")]
13051305
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
1306+
#[cfg(not(bootstrap))]
1307+
pub const fn align_offset(self, align: usize) -> usize
1308+
where
1309+
T: Sized,
1310+
{
1311+
assert!(align.is_power_of_two(), "align_offset: align is not a power-of-two");
1312+
1313+
// SAFETY: `align` has been checked to be a power of 2 above
1314+
unsafe { align_offset(self, align) }
1315+
}
1316+
1317+
#[stable(feature = "align_offset", since = "1.36.0")]
1318+
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
1319+
#[allow(missing_docs)]
1320+
#[cfg(bootstrap)]
13061321
pub const fn align_offset(self, align: usize) -> usize
13071322
where
13081323
T: Sized,

library/core/src/ptr/mod.rs

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

15551555
/// Align pointer `p`.
15561556
///
1557-
/// Calculate offset (in terms of elements of `stride` stride) that has to be applied
1557+
/// Calculate offset (in terms of elements of `size_of::<T>()` stride) that has to be applied
15581558
/// to pointer `p` so that pointer `p` would get aligned to `a`.
15591559
///
1560-
/// Note: This implementation has been carefully tailored to not panic. It is UB for this to panic.
1560+
/// # Safety
1561+
/// `a` must be a power of two.
1562+
///
1563+
/// # Notes
1564+
/// This implementation has been carefully tailored to not panic. It is UB for this to panic.
15611565
/// The only real change that can be made here is change of `INV_TABLE_MOD_16` and associated
15621566
/// constants.
15631567
///
@@ -1566,8 +1570,10 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15661570
/// than trying to adapt this to accommodate that change.
15671571
///
15681572
/// Any questions go to @nagisa.
1573+
// #[cfg(not(bootstrap))] -- Calling this function in a const context from the bootstrap
1574+
// compiler will always cause an error.
15691575
#[lang = "align_offset"]
1570-
pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
1576+
pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
15711577
// FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=
15721578
// 1, where the method versions of these operations are not inlined.
15731579
use intrinsics::{
@@ -1584,7 +1590,7 @@ pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
15841590
///
15851591
/// Implementation of this function shall not panic. Ever.
15861592
#[inline]
1587-
unsafe fn mod_inv(x: usize, m: usize) -> usize {
1593+
const unsafe fn mod_inv(x: usize, m: usize) -> usize {
15881594
/// Multiplicative modular inverse table modulo 2⁴ = 16.
15891595
///
15901596
/// Note, that this table does not contain values where inverse does not exist (i.e., for
@@ -1624,8 +1630,13 @@ pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
16241630
}
16251631
}
16261632

1627-
let addr = p.addr();
16281633
let stride = mem::size_of::<T>();
1634+
1635+
// SAFETY: At runtime transmuting a pointer to `usize` is always safe, because they have the
1636+
// same layout. During const eval we hook this function to ensure that the pointer always has
1637+
// an address (only the standard library can do this).
1638+
let addr = unsafe { mem::transmute(p) };
1639+
16291640
// SAFETY: `a` is a power-of-two, therefore non-zero.
16301641
let a_minus_one = unsafe { unchecked_sub(a, 1) };
16311642

library/core/src/ptr/mut_ptr.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,21 @@ impl<T: ?Sized> *mut T {
15741574
/// ```
15751575
#[stable(feature = "align_offset", since = "1.36.0")]
15761576
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
1577+
#[cfg(not(bootstrap))]
1578+
pub const fn align_offset(self, align: usize) -> usize
1579+
where
1580+
T: Sized,
1581+
{
1582+
assert!(align.is_power_of_two(), "align_offset: align is not a power-of-two");
1583+
1584+
// SAFETY: `align` has been checked to be a power of 2 above
1585+
unsafe { align_offset(self, align) }
1586+
}
1587+
1588+
#[stable(feature = "align_offset", since = "1.36.0")]
1589+
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
1590+
#[allow(missing_docs)]
1591+
#[cfg(bootstrap)]
15771592
pub const fn align_offset(self, align: usize) -> usize
15781593
where
15791594
T: Sized,

0 commit comments

Comments
 (0)