Skip to content

Commit 62c7fff

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 ee9c7c9 commit 62c7fff

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;
@@ -187,6 +189,16 @@ impl interpret::MayLeak for ! {
187189
}
188190
}
189191

192+
#[derive(Debug, Copy, Clone)]
193+
pub enum ExtraFnVal<'tcx> {
194+
/// `#[rustc_const_panic_str]` or `#[lang = "begin_panic"]`
195+
BeginPanic,
196+
/// `#[lang = "panic_fmt"]`
197+
PanicFmt(ty::Instance<'tcx>),
198+
/// `#[lang = "align_offset"]`
199+
AlignOffset(ty::Instance<'tcx>),
200+
}
201+
190202
impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
191203
fn location_triple_for_span(&self, span: Span) -> (Symbol, u32, u32) {
192204
let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span);
@@ -208,56 +220,29 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
208220

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

223231
if self.tcx.has_attr(def_id, sym::rustc_const_panic_str)
224232
|| Some(def_id) == self.tcx.lang_items().begin_panic_fn()
225233
{
226-
let args = self.copy_fn_args(args)?;
227-
// &str or &&str
228-
assert!(args.len() == 1);
234+
return Some(ExtraFnVal::BeginPanic);
235+
}
229236

230-
let mut msg_place = self.deref_pointer(&args[0])?;
231-
while msg_place.layout.ty.is_ref() {
232-
msg_place = self.deref_pointer(&msg_place)?;
233-
}
237+
if Some(def_id) == self.tcx.lang_items().panic_fmt() {
238+
return Some(ExtraFnVal::PanicFmt(instance));
239+
}
234240

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

263248
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
@@ -367,6 +352,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
367352
compile_time_machine!(<'mir, 'tcx>);
368353

369354
type MemoryKind = MemoryKind;
355+
type ExtraFnVal = ExtraFnVal<'tcx>;
370356

371357
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
372358

@@ -395,7 +381,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
395381
.delayed_bug("This is likely a const item that is missing from its impl");
396382
throw_inval!(AlreadyReported(guar.into()));
397383
} else {
398-
// `find_mir_or_eval_fn` checks that this is a const fn before even calling us,
384+
// `find_mir_or_extra_fn` checks that this is a const fn before even calling us,
399385
// so this should be unreachable.
400386
let path = ecx.tcx.def_path_str(def);
401387
bug!("trying to call extern function `{path}` at compile-time");
@@ -405,22 +391,17 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
405391
}
406392
}
407393

408-
fn find_mir_or_eval_fn(
394+
fn find_mir_or_extra_fn(
409395
ecx: &mut InterpCx<'mir, 'tcx, Self>,
410-
orig_instance: ty::Instance<'tcx>,
396+
instance: ty::Instance<'tcx>,
411397
_abi: CallAbi,
412-
args: &[FnArg<'tcx>],
413-
dest: &PlaceTy<'tcx>,
414-
ret: Option<mir::BasicBlock>,
415-
_unwind: mir::UnwindAction, // unwinding is not supported in consts
416-
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
417-
debug!("find_mir_or_eval_fn: {:?}", orig_instance);
398+
) -> InterpResult<'tcx, Either<&'mir mir::Body<'tcx>, Self::ExtraFnVal>> {
399+
debug!("find_mir_or_extra_fn: {:?}", instance);
418400

419401
// Replace some functions.
420-
let Some(instance) = ecx.hook_special_const_fn(orig_instance, args, dest, ret)? else {
421-
// Call has already been handled.
422-
return Ok(None);
423-
};
402+
if let Some(extra) = ecx.hook_special_const_fn(instance) {
403+
return Ok(Either::Right(extra));
404+
}
424405

425406
// Only check non-glue functions
426407
if let ty::InstanceDef::Item(def) = instance.def {
@@ -438,10 +419,75 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
438419
}
439420
}
440421

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

447493
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,
@@ -214,7 +214,8 @@ pub fn intern_const_alloc_for_constprop<
214214
'mir,
215215
'tcx: 'mir,
216216
T,
217-
M: CompileTimeMachine<'mir, 'tcx, T>,
217+
U,
218+
M: CompileTimeMachine<'mir, 'tcx, T, U>,
218219
>(
219220
ecx: &mut InterpCx<'mir, 'tcx, M>,
220221
alloc_id: AllocId,
@@ -233,7 +234,7 @@ pub fn intern_const_alloc_for_constprop<
233234
Ok(())
234235
}
235236

236-
impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
237+
impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !, !>>
237238
InterpCx<'mir, 'tcx, M>
238239
{
239240
/// 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>,
@@ -540,8 +537,6 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
540537
type Provenance = CtfeProvenance;
541538
type ProvenanceExtra = bool; // the "immutable" flag
542539

543-
type ExtraFnVal = !;
544-
545540
type MemoryMap =
546541
rustc_data_structures::fx::FxIndexMap<AllocId, (MemoryKind<Self::MemoryKind>, Allocation)>;
547542
const GLOBAL_KIND: Option<Self::MemoryKind> = None; // no copying of globals from `tcx` to machine memory
@@ -563,19 +558,6 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
563558
unreachable!("unwinding cannot happen during compile-time evaluation")
564559
}
565560

566-
#[inline(always)]
567-
fn call_extra_fn(
568-
_ecx: &mut InterpCx<$mir, $tcx, Self>,
569-
fn_val: !,
570-
_abi: CallAbi,
571-
_args: &[FnArg<$tcx>],
572-
_destination: &PlaceTy<$tcx, Self::Provenance>,
573-
_target: Option<mir::BasicBlock>,
574-
_unwind: mir::UnwindAction,
575-
) -> InterpResult<$tcx> {
576-
match fn_val {}
577-
}
578-
579561
#[inline(always)]
580562
fn adjust_allocation<'b>(
581563
_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)