Skip to content

Commit c4ecc2b

Browse files
committed
Refactor the way interpreters handle special functions
By reusing the `ExtraFnVal` type. This changes `find_mir_or_eval_fn` into `find_mir_or_extra_fn`, which helps structure code a little bit better. Having separate "find info for evaluation" and "evaluate" steps is nice on its own, but it's also required for tail calls.
1 parent c9c83cc commit c4ecc2b

File tree

6 files changed

+149
-106
lines changed

6 files changed

+149
-106
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

+105-59
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt;
33
use std::hash::Hash;
44
use std::ops::ControlFlow;
55

6+
use either::Either;
67
use rustc_ast::Mutability;
78
use rustc_data_structures::fx::FxIndexMap;
89
use rustc_data_structures::fx::IndexEntry;
@@ -14,6 +15,7 @@ use rustc_middle::mir::AssertMessage;
1415
use rustc_middle::query::TyCtxtAt;
1516
use rustc_middle::ty;
1617
use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout};
18+
use rustc_middle::ty::Ty;
1719
use rustc_session::lint::builtin::WRITES_THROUGH_IMMUTABLE_POINTER;
1820
use rustc_span::symbol::{sym, Symbol};
1921
use rustc_span::Span;
@@ -191,6 +193,16 @@ impl interpret::MayLeak for ! {
191193
}
192194
}
193195

196+
#[derive(Debug, Copy, Clone)]
197+
pub enum ExtraFnVal<'tcx> {
198+
/// `#[rustc_const_panic_str]` or `#[lang = "begin_panic"]`
199+
BeginPanic,
200+
/// `#[lang = "panic_fmt"]`
201+
PanicFmt(ty::Instance<'tcx>),
202+
/// `#[lang = "align_offset"]`
203+
AlignOffset(ty::Instance<'tcx>),
204+
}
205+
194206
impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
195207
fn location_triple_for_span(&self, span: Span) -> (Symbol, u32, u32) {
196208
let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span);
@@ -212,56 +224,29 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
212224

213225
/// "Intercept" a function call, because we have something special to do for it.
214226
/// All `#[rustc_do_not_const_check]` functions should be hooked here.
215-
/// If this returns `Some` function, which may be `instance` or a different function with
216-
/// compatible arguments, then evaluation should continue with that function.
217-
/// If this returns `None`, the function call has been handled and the function has returned.
218-
fn hook_special_const_fn(
219-
&mut self,
220-
instance: ty::Instance<'tcx>,
221-
args: &[FnArg<'tcx>],
222-
dest: &PlaceTy<'tcx>,
223-
ret: Option<mir::BasicBlock>,
224-
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
227+
///
228+
/// If this returns `Some`, the function should be executed via [`call_extra_fn`].
229+
/// If this returns `None`, the function should be executed as normal.
230+
///
231+
/// [`call_extra_fn`]: interpret::Machine::call_extra_fn
232+
fn hook_special_const_fn(&mut self, instance: ty::Instance<'tcx>) -> Option<ExtraFnVal<'tcx>> {
225233
let def_id = instance.def_id();
226234

227235
if self.tcx.has_attr(def_id, sym::rustc_const_panic_str)
228236
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
229237
{
230-
let args = self.copy_fn_args(args)?;
231-
// &str or &&str
232-
assert!(args.len() == 1);
238+
return Some(ExtraFnVal::BeginPanic);
239+
}
233240

234-
let mut msg_place = self.deref_pointer(&args[0])?;
235-
while msg_place.layout.ty.is_ref() {
236-
msg_place = self.deref_pointer(&msg_place)?;
237-
}
241+
if Some(def_id) == self.tcx.lang_items().panic_fmt() {
242+
return Some(ExtraFnVal::PanicFmt(instance));
243+
}
238244

239-
let msg = Symbol::intern(self.read_str(&msg_place)?);
240-
let span = self.find_closest_untracked_caller_location();
241-
let (file, line, col) = self.location_triple_for_span(span);
242-
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
243-
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
244-
// For panic_fmt, call const_panic_fmt instead.
245-
let const_def_id = self.tcx.require_lang_item(LangItem::ConstPanicFmt, None);
246-
let new_instance = ty::Instance::resolve(
247-
*self.tcx,
248-
ty::ParamEnv::reveal_all(),
249-
const_def_id,
250-
instance.args,
251-
)
252-
.unwrap()
253-
.unwrap();
254-
255-
return Ok(Some(new_instance));
256-
} else if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
257-
let args = self.copy_fn_args(args)?;
258-
// For align_offset, we replace the function call if the pointer has no address.
259-
match self.align_offset(instance, &args, dest, ret)? {
260-
ControlFlow::Continue(()) => return Ok(Some(instance)),
261-
ControlFlow::Break(()) => return Ok(None),
262-
}
245+
if Some(def_id) == self.tcx.lang_items().align_offset_fn() {
246+
return Some(ExtraFnVal::AlignOffset(instance));
263247
}
264-
Ok(Some(instance))
248+
249+
None
265250
}
266251

267252
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
@@ -371,6 +356,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
371356
compile_time_machine!(<'mir, 'tcx>);
372357

373358
type MemoryKind = MemoryKind;
359+
type ExtraFnVal = ExtraFnVal<'tcx>;
374360

375361
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
376362

@@ -399,7 +385,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
399385
.delayed_bug("This is likely a const item that is missing from its impl");
400386
throw_inval!(AlreadyReported(guar.into()));
401387
} else {
402-
// `find_mir_or_eval_fn` checks that this is a const fn before even calling us,
388+
// `find_mir_or_extra_fn` checks that this is a const fn before even calling us,
403389
// so this should be unreachable.
404390
let path = ecx.tcx.def_path_str(def);
405391
bug!("trying to call extern function `{path}` at compile-time");
@@ -409,22 +395,17 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
409395
}
410396
}
411397

412-
fn find_mir_or_eval_fn(
398+
fn find_mir_or_extra_fn(
413399
ecx: &mut InterpCx<'mir, 'tcx, Self>,
414-
orig_instance: ty::Instance<'tcx>,
400+
instance: ty::Instance<'tcx>,
415401
_abi: CallAbi,
416-
args: &[FnArg<'tcx>],
417-
dest: &PlaceTy<'tcx>,
418-
ret: Option<mir::BasicBlock>,
419-
_unwind: mir::UnwindAction, // unwinding is not supported in consts
420-
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
421-
debug!("find_mir_or_eval_fn: {:?}", orig_instance);
402+
) -> InterpResult<'tcx, Either<&'mir mir::Body<'tcx>, Self::ExtraFnVal>> {
403+
debug!("find_mir_or_extra_fn: {:?}", instance);
422404

423405
// Replace some functions.
424-
let Some(instance) = ecx.hook_special_const_fn(orig_instance, args, dest, ret)? else {
425-
// Call has already been handled.
426-
return Ok(None);
427-
};
406+
if let Some(extra) = ecx.hook_special_const_fn(instance) {
407+
return Ok(Either::Right(extra));
408+
}
428409

429410
// Only check non-glue functions
430411
if let ty::InstanceDef::Item(def) = instance.def {
@@ -442,10 +423,75 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
442423
}
443424
}
444425

445-
// This is a const fn. Call it.
446-
// In case of replacement, we return the *original* instance to make backtraces work out
447-
// (and we hope this does not confuse the FnAbi checks too much).
448-
Ok(Some((ecx.load_mir(instance.def, None)?, orig_instance)))
426+
// This is a const fn. Return its mir to be called.
427+
ecx.load_mir(instance.def, None).map(Either::Left)
428+
}
429+
430+
#[inline(always)]
431+
fn call_extra_fn(
432+
ecx: &mut InterpCx<'mir, 'tcx, Self>,
433+
fn_val: Self::ExtraFnVal,
434+
abis: (CallAbi, &rustc_target::abi::call::FnAbi<'tcx, Ty<'tcx>>),
435+
args: &[FnArg<'tcx>],
436+
destination: &PlaceTy<'tcx, Self::Provenance>,
437+
target: Option<mir::BasicBlock>,
438+
unwind: mir::UnwindAction,
439+
) -> InterpResult<'tcx> {
440+
match fn_val {
441+
ExtraFnVal::BeginPanic => {
442+
let args = ecx.copy_fn_args(args)?;
443+
// &str or &&str
444+
assert!(args.len() == 1);
445+
446+
let mut msg_place = ecx.deref_pointer(&args[0])?;
447+
while msg_place.layout.ty.is_ref() {
448+
msg_place = ecx.deref_pointer(&msg_place)?;
449+
}
450+
451+
let msg = Symbol::intern(ecx.read_str(&msg_place)?);
452+
let span = ecx.find_closest_untracked_caller_location();
453+
let (file, line, col) = ecx.location_triple_for_span(span);
454+
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
455+
}
456+
ExtraFnVal::PanicFmt(instance) => {
457+
// For panic_fmt, call const_panic_fmt instead.
458+
let const_def_id = ecx.tcx.require_lang_item(LangItem::ConstPanicFmt, None);
459+
let new_instance = ty::Instance::resolve(
460+
*ecx.tcx,
461+
ty::ParamEnv::reveal_all(),
462+
const_def_id,
463+
instance.args,
464+
)
465+
.unwrap()
466+
.unwrap();
467+
468+
ecx.eval_fn_call(
469+
FnVal::Instance(new_instance),
470+
abis,
471+
args,
472+
true,
473+
destination,
474+
target,
475+
unwind,
476+
)
477+
}
478+
ExtraFnVal::AlignOffset(instance) => {
479+
let args2 = ecx.copy_fn_args(args)?;
480+
// For align_offset, we replace the function call if the pointer has no address.
481+
match ecx.align_offset(instance, &args2, destination, target)? {
482+
ControlFlow::Continue(()) => ecx.eval_fn_call(
483+
FnVal::Instance(instance),
484+
abis,
485+
args,
486+
false,
487+
destination,
488+
target,
489+
unwind,
490+
),
491+
ControlFlow::Break(()) => Ok(()),
492+
}
493+
}
494+
}
449495
}
450496

451497
fn panic_nounwind(ecx: &mut InterpCx<'mir, 'tcx, Self>, msg: &str) -> InterpResult<'tcx> {

compiler/rustc_const_eval/src/interpret/intern.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceT
2424
use crate::const_eval;
2525
use crate::errors::{DanglingPtrInFinal, MutablePtrInFinal};
2626

27-
pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine<
27+
pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T, U = const_eval::ExtraFnVal<'tcx>> = Machine<
2828
'mir,
2929
'tcx,
3030
MemoryKind = T,
3131
Provenance = CtfeProvenance,
32-
ExtraFnVal = !,
32+
ExtraFnVal = U,
3333
FrameExtra = (),
3434
AllocExtra = (),
3535
MemoryMap = FxIndexMap<AllocId, (MemoryKind<T>, Allocation)>,
@@ -42,7 +42,7 @@ pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine<
4242
/// already mutable (as a sanity check).
4343
///
4444
/// Returns an iterator over all relocations referred to by this allocation.
45-
fn intern_shallow<'rt, 'mir, 'tcx, T, M: CompileTimeMachine<'mir, 'tcx, T>>(
45+
fn intern_shallow<'rt, 'mir, 'tcx, T, U, M: CompileTimeMachine<'mir, 'tcx, T, U>>(
4646
ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
4747
alloc_id: AllocId,
4848
mutability: Mutability,
@@ -236,7 +236,8 @@ pub fn intern_const_alloc_for_constprop<
236236
'mir,
237237
'tcx: 'mir,
238238
T,
239-
M: CompileTimeMachine<'mir, 'tcx, T>,
239+
U,
240+
M: CompileTimeMachine<'mir, 'tcx, T, U>,
240241
>(
241242
ecx: &mut InterpCx<'mir, 'tcx, M>,
242243
alloc_id: AllocId,
@@ -255,7 +256,7 @@ pub fn intern_const_alloc_for_constprop<
255256
Ok(())
256257
}
257258

258-
impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
259+
impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !, !>>
259260
InterpCx<'mir, 'tcx, M>
260261
{
261262
/// A helper function that allocates memory for the layout given and gives you access to mutate

compiler/rustc_const_eval/src/interpret/machine.rs

+5-23
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ use std::borrow::{Borrow, Cow};
66
use std::fmt::Debug;
77
use std::hash::Hash;
88

9+
use either::Either;
910
use rustc_apfloat::{Float, FloatConvert};
1011
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
1112
use rustc_middle::mir;
1213
use rustc_middle::query::TyCtxtAt;
13-
use rustc_middle::ty;
1414
use rustc_middle::ty::layout::TyAndLayout;
15+
use rustc_middle::ty::{self, Ty};
1516
use rustc_span::def_id::DefId;
1617
use rustc_span::Span;
1718
use rustc_target::abi::{Align, Size};
@@ -191,22 +192,18 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
191192
/// nor just jump to `ret`, but instead push their own stack frame.)
192193
/// Passing `dest`and `ret` in the same `Option` proved very annoying when only one of them
193194
/// was used.
194-
fn find_mir_or_eval_fn(
195+
fn find_mir_or_extra_fn(
195196
ecx: &mut InterpCx<'mir, 'tcx, Self>,
196197
instance: ty::Instance<'tcx>,
197198
abi: CallAbi,
198-
args: &[FnArg<'tcx, Self::Provenance>],
199-
destination: &PlaceTy<'tcx, Self::Provenance>,
200-
target: Option<mir::BasicBlock>,
201-
unwind: mir::UnwindAction,
202-
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>>;
199+
) -> InterpResult<'tcx, Either<&'mir mir::Body<'tcx>, Self::ExtraFnVal>>;
203200

204201
/// Execute `fn_val`. It is the hook's responsibility to advance the instruction
205202
/// pointer as appropriate.
206203
fn call_extra_fn(
207204
ecx: &mut InterpCx<'mir, 'tcx, Self>,
208205
fn_val: Self::ExtraFnVal,
209-
abi: CallAbi,
206+
abis: (CallAbi, &rustc_target::abi::call::FnAbi<'tcx, Ty<'tcx>>),
210207
args: &[FnArg<'tcx, Self::Provenance>],
211208
destination: &PlaceTy<'tcx, Self::Provenance>,
212209
target: Option<mir::BasicBlock>,
@@ -555,8 +552,6 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
555552
type Provenance = CtfeProvenance;
556553
type ProvenanceExtra = bool; // the "immutable" flag
557554

558-
type ExtraFnVal = !;
559-
560555
type MemoryMap =
561556
rustc_data_structures::fx::FxIndexMap<AllocId, (MemoryKind<Self::MemoryKind>, Allocation)>;
562557
const GLOBAL_KIND: Option<Self::MemoryKind> = None; // no copying of globals from `tcx` to machine memory
@@ -578,19 +573,6 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
578573
unreachable!("unwinding cannot happen during compile-time evaluation")
579574
}
580575

581-
#[inline(always)]
582-
fn call_extra_fn(
583-
_ecx: &mut InterpCx<$mir, $tcx, Self>,
584-
fn_val: !,
585-
_abi: CallAbi,
586-
_args: &[FnArg<$tcx>],
587-
_destination: &PlaceTy<$tcx, Self::Provenance>,
588-
_target: Option<mir::BasicBlock>,
589-
_unwind: mir::UnwindAction,
590-
) -> InterpResult<$tcx> {
591-
match fn_val {}
592-
}
593-
594576
#[inline(always)]
595577
fn adjust_allocation<'b>(
596578
_ecx: &InterpCx<$mir, $tcx, Self>,

compiler/rustc_const_eval/src/interpret/terminator.rs

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22

3+
use either::Either;
34
use rustc_ast::ast::InlineAsmOptions;
45
use rustc_middle::{
56
mir,
@@ -515,7 +516,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
515516
return M::call_extra_fn(
516517
self,
517518
extra,
518-
caller_abi,
519+
(caller_abi, caller_fn_abi),
519520
args,
520521
destination,
521522
target,
@@ -549,21 +550,23 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
549550
| ty::InstanceDef::ThreadLocalShim(..)
550551
| ty::InstanceDef::Item(_) => {
551552
// We need MIR for this fn
552-
let Some((body, instance)) = M::find_mir_or_eval_fn(
553-
self,
554-
instance,
555-
caller_abi,
556-
args,
557-
destination,
558-
target,
559-
unwind,
560-
)?
561-
else {
562-
return Ok(());
553+
let body = match M::find_mir_or_extra_fn(self, instance, caller_abi)? {
554+
Either::Left(b) => b,
555+
Either::Right(f) => {
556+
return M::call_extra_fn(
557+
self,
558+
f,
559+
(caller_abi, caller_fn_abi),
560+
args,
561+
destination,
562+
target,
563+
unwind,
564+
);
565+
}
563566
};
564567

565568
// Compute callee information using the `instance` returned by
566-
// `find_mir_or_eval_fn`.
569+
// `find_mir_or_extra_fn`.
567570
// FIXME: for variadic support, do we have to somehow determine callee's extra_args?
568571
let callee_fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
569572

0 commit comments

Comments
 (0)