Skip to content

Commit c5d82ed

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

File tree

11 files changed

+984
-97
lines changed

11 files changed

+984
-97
lines changed

Diff for: compiler/rustc_const_eval/src/const_eval/machine.rs

+135-53
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
}

Diff for: compiler/rustc_const_eval/src/interpret/intrinsics.rs

+5
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

Diff for: library/core/src/intrinsics.rs

+1
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

Diff for: library/core/src/lib.rs

+2
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)]

0 commit comments

Comments
 (0)