Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 3d7e9c4

Browse files
author
Lukas Markeffsky
committed
Revert "don't call align_offset during const eval, ever"
This reverts commit f3a577bfae376c0222e934911865ed14cddd1539.
1 parent 9e5d497 commit 3d7e9c4

File tree

2 files changed

+49
-92
lines changed

2 files changed

+49
-92
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 44 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ use rustc_hir::def::DefKind;
22
use rustc_hir::LangItem;
33
use rustc_middle::mir;
44
use rustc_middle::mir::interpret::PointerArithmetic;
5-
use rustc_middle::ty::layout::LayoutOf;
5+
use rustc_middle::ty::layout::FnAbiOf;
66
use rustc_middle::ty::{self, Ty, TyCtxt};
77
use std::borrow::Borrow;
88
use std::hash::Hash;
9+
use std::ops::ControlFlow;
910

1011
use rustc_data_structures::fx::FxIndexMap;
1112
use rustc_data_structures::fx::IndexEntry;
@@ -20,8 +21,8 @@ use rustc_target::abi::{Align, Size};
2021
use rustc_target::spec::abi::Abi as CallAbi;
2122

2223
use crate::interpret::{
23-
self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
24-
OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
24+
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
25+
InterpResult, OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
2526
};
2627

2728
use super::error::*;
@@ -191,21 +192,24 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
191192

192193
return Ok(Some(new_instance));
193194
} else if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
194-
// For align_offset, we replace the function call entirely.
195-
self.align_offset(instance, args, dest, ret)?;
196-
return Ok(None);
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+
}
197200
}
198201
Ok(Some(instance))
199202
}
200203

201-
/// This function replaces `align_offset(ptr, target_align)` in const eval, because the
202-
/// pointer may not have an address.
204+
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
205+
/// may not have an address.
203206
///
204-
/// If `ptr` does have a known address, we forward it to [`Self::align_offset_impl`].
207+
/// If `ptr` does have a known address, then we return `CONTINUE` and the function call should
208+
/// proceed as normal.
205209
///
206210
/// If `ptr` doesn't have an address, but its underlying allocation's alignment is at most
207-
/// `target_align`, then we call [`Self::align_offset_impl`] with an dummy address relative
208-
/// to the allocation.
211+
/// `target_align`, then we call the function again with an dummy address relative to the
212+
/// allocation.
209213
///
210214
/// If `ptr` doesn't have an address and `target_align` is stricter than the underlying
211215
/// allocation's alignment, then we return `usize::MAX` immediately.
@@ -215,103 +219,53 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
215219
args: &[OpTy<'tcx>],
216220
dest: &PlaceTy<'tcx>,
217221
ret: Option<mir::BasicBlock>,
218-
) -> InterpResult<'tcx> {
222+
) -> InterpResult<'tcx, ControlFlow<()>> {
219223
assert_eq!(args.len(), 2);
220224

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

224-
let pointee_ty = instance.substs.type_at(0);
225-
let stride = self.layout_of(pointee_ty)?.size.bytes();
226-
227228
if !target_align.is_power_of_two() {
228229
throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align);
229230
}
230231

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

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

317271
/// See documentation on the `ptr_guaranteed_cmp` intrinsic.

library/core/src/ptr/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,6 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15911591
///
15921592
/// Any questions go to @nagisa.
15931593
#[lang = "align_offset"]
1594-
#[rustc_do_not_const_check] // hooked by const-eval
15951594
pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
15961595
// FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=
15971596
// 1, where the method versions of these operations are not inlined.
@@ -1651,9 +1650,13 @@ pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usiz
16511650
inverse & m_minus_one
16521651
}
16531652

1654-
let addr = p.addr();
16551653
let stride = mem::size_of::<T>();
16561654

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

0 commit comments

Comments
 (0)