Skip to content

Commit a906f6c

Browse files
author
Lukas Markeffsky
committed
don't call align_offset during const eval, ever
1 parent 24e8806 commit a906f6c

File tree

2 files changed

+90
-50
lines changed

2 files changed

+90
-50
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use rustc_hir::def::DefKind;
22
use rustc_middle::mir;
33
use rustc_middle::mir::interpret::PointerArithmetic;
4-
use rustc_middle::ty::layout::FnAbiOf;
4+
use rustc_middle::ty::layout::LayoutOf;
55
use rustc_middle::ty::{self, Ty, TyCtxt};
66
use std::borrow::Borrow;
77
use std::hash::Hash;
8-
use std::ops::ControlFlow;
98

109
use rustc_data_structures::fx::FxIndexMap;
1110
use rustc_data_structures::fx::IndexEntry;
@@ -20,8 +19,8 @@ use rustc_target::abi::{Align, Size};
2019
use rustc_target::spec::abi::Abi as CallAbi;
2120

2221
use crate::interpret::{
23-
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
24-
InterpResult, OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
22+
self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
23+
OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
2524
};
2625

2726
use super::error::*;
@@ -156,7 +155,6 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
156155
fn hook_special_const_fn(
157156
&mut self,
158157
instance: ty::Instance<'tcx>,
159-
_abi: CallAbi,
160158
args: &[OpTy<'tcx>],
161159
dest: &PlaceTy<'tcx>,
162160
ret: Option<mir::BasicBlock>,
@@ -194,24 +192,21 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
194192

195193
return Ok(Some(new_instance));
196194
} 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-
}
195+
// For align_offset, we replace the function call entirely.
196+
self.align_offset(instance, args, dest, ret)?;
197+
return Ok(None);
202198
}
203199
Ok(Some(instance))
204200
}
205201

206-
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
207-
/// may not have an address.
202+
/// This function replaces `align_offset(ptr, target_align)` in const eval, because the
203+
/// pointer may not have an address.
208204
///
209-
/// If `ptr` does have a known address, then we return `CONTINUE` and the function call should
210-
/// proceed as normal.
205+
/// If `ptr` does have a known address, we forward it to [`Self::align_offset_impl`].
211206
///
212207
/// 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.
208+
/// `target_align`, then we call [`Self::align_offset_impl`] with an dummy address relative
209+
/// to the allocation.
215210
///
216211
/// If `ptr` doesn't have an address and `target_align` is stricter than the underlying
217212
/// allocation's alignment, then we return `usize::MAX` immediately.
@@ -221,50 +216,100 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
221216
args: &[OpTy<'tcx>],
222217
dest: &PlaceTy<'tcx>,
223218
ret: Option<mir::BasicBlock>,
224-
) -> InterpResult<'tcx, ControlFlow<()>> {
219+
) -> InterpResult<'tcx> {
225220
assert_eq!(args.len(), 2);
226221

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

225+
let pointee_ty = instance.substs.type_at(0);
226+
let stride = self.layout_of(pointee_ty)?.size.bytes();
227+
230228
if !target_align.is_power_of_two() {
231229
throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
232230
}
233231

234-
match self.ptr_try_get_alloc_id(ptr) {
232+
let mut align_offset = match self.ptr_try_get_alloc_id(ptr) {
235233
Ok((alloc_id, offset, _extra)) => {
234+
// Extract the address relative to a base that is definitely sufficiently aligned.
236235
let (_size, alloc_align, _kind) = self.get_alloc_info(alloc_id);
237236

238237
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)
238+
// The pointer *is* alignable in const. We use an address relative to the
239+
// allocation base that is definitely sufficiently aligned.
240+
let addr = offset.bytes();
241+
Self::align_offset_impl(addr, stride, target_align)
255242
} 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)
243+
// The pointer *is not* alignable in const, return `usize::MAX`.
244+
// (We clamp this to machine `usize` below.)
245+
u64::MAX
261246
}
262247
}
263-
Err(_addr) => {
264-
// The pointer has an address, continue with function call.
265-
Ok(ControlFlow::CONTINUE)
248+
Err(addr) => {
249+
// The pointer has a known address.
250+
Self::align_offset_impl(addr, stride, target_align)
266251
}
252+
};
253+
254+
let usize_max = self.machine_usize_max();
255+
if align_offset > usize_max {
256+
align_offset = usize_max;
257+
}
258+
259+
self.write_scalar(Scalar::from_machine_usize(align_offset, self), dest)?;
260+
self.return_to_block(ret)?;
261+
262+
Ok(())
263+
}
264+
265+
/// Const eval implementation of `#[lang = "align_offset"]`.
266+
/// See the runtime version for a detailed explanation how this works.
267+
fn align_offset_impl(addr: u64, stride: u64, align: u64) -> u64 {
268+
assert!(align.is_power_of_two());
269+
270+
let addr_mod_align = addr % align;
271+
272+
if addr_mod_align == 0 {
273+
// The address is already sufficiently aligned.
274+
return 0;
267275
}
276+
277+
if stride == 0 {
278+
// The address cannot be aligned.
279+
return u64::MAX;
280+
}
281+
282+
let byte_offset = align - addr_mod_align;
283+
284+
if align % stride == 0 {
285+
if byte_offset % stride == 0 {
286+
return byte_offset / stride;
287+
} else {
288+
return u64::MAX;
289+
}
290+
}
291+
292+
// This only works, because `align` is a power of two.
293+
let gcd = 1u64 << (stride | align).trailing_zeros();
294+
295+
if addr % gcd != 0 {
296+
// The address cannot be aligned.
297+
return u64::MAX;
298+
}
299+
300+
let align2 = align / gcd;
301+
let stride2 = (stride / gcd) % align2;
302+
303+
let mut stride_inv = 1u64;
304+
let mut mod_gate = 2u64;
305+
let mut overflow = false;
306+
while !overflow && mod_gate < align2 {
307+
stride_inv =
308+
stride_inv.wrapping_mul(2u64.wrapping_sub(stride2.wrapping_mul(stride_inv)));
309+
(mod_gate, overflow) = mod_gate.overflowing_mul(mod_gate);
310+
}
311+
312+
byte_offset.wrapping_mul(stride_inv) % align2
268313
}
269314

270315
/// See documentation on the `ptr_guaranteed_cmp` intrinsic.
@@ -367,7 +412,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
367412
}
368413
}
369414

370-
let Some(new_instance) = ecx.hook_special_const_fn(instance, _abi, args, dest, ret)? else {
415+
let Some(new_instance) = ecx.hook_special_const_fn(instance, args, dest, ret)? else {
371416
return Ok(None);
372417
};
373418

library/core/src/ptr/mod.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,9 +1590,8 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15901590
/// than trying to adapt this to accommodate that change.
15911591
///
15921592
/// Any questions go to @nagisa.
1593-
// #[cfg(not(bootstrap))] -- Calling this function in a const context from the bootstrap
1594-
// compiler will always cause an error.
15951593
#[lang = "align_offset"]
1594+
#[rustc_do_not_const_check] // hooked by const-eval
15961595
pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
15971596
// FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=
15981597
// 1, where the method versions of these operations are not inlined.
@@ -1652,13 +1651,9 @@ pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usiz
16521651
inverse & m_minus_one
16531652
}
16541653

1654+
let addr = p.addr();
16551655
let stride = mem::size_of::<T>();
16561656

1657-
// SAFETY: At runtime transmuting a pointer to `usize` is always safe, because they have the
1658-
// same layout. During const eval we hook this function to ensure that the pointer always has
1659-
// an address (only the standard library can do this).
1660-
let addr = unsafe { mem::transmute(p) };
1661-
16621657
// SAFETY: `a` is a power-of-two, therefore non-zero.
16631658
let a_minus_one = unsafe { unchecked_sub(a, 1) };
16641659

0 commit comments

Comments
 (0)