Skip to content

Commit de839fc

Browse files
committed
Do not deduce parameter attributes during CTFE
Deducing parameter attributes can depend upon typeck, so can lead to cycles if performed during constant evaluation. This patch breaks a subquery out from `fn_abi_of_interface`, `fn_abi_of_interface_unadjusted`, which returns the ABI before deduced parameter attributes are applied; and that new subquery is used in CTFE instead.
1 parent ba284f4 commit de839fc

7 files changed

Lines changed: 177 additions & 61 deletions

File tree

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
351351

352352
// Compute callee information.
353353
// FIXME: for variadic support, do we have to somehow determine callee's extra_args?
354-
let callee_fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
354+
let callee_fn_abi = self.fn_abi_of_instance_unadjusted(instance, ty::List::empty())?;
355355

356356
if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
357357
throw_unsup_format!("calling a c-variadic function is not supported");
@@ -839,7 +839,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
839839
enter_trace_span!(M, resolve::resolve_drop_in_place, ty = ?place.layout.ty);
840840
ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty)
841841
};
842-
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
842+
let fn_abi = self.fn_abi_of_instance_unadjusted(instance, ty::List::empty())?;
843843

844844
let arg = self.mplace_to_ref(&place)?;
845845
let ret = MPlaceTy::fake_alloc_zst(self.layout_of(self.tcx.types.unit)?);

compiler/rustc_const_eval/src/interpret/eval_context.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
153153
}
154154

155155
/// This inherent method takes priority over the trait method with the same name in FnAbiOf,
156-
/// and allows wrapping the actual [FnAbiOf::fn_abi_of_instance] with a tracing span.
157-
/// See [FnAbiOf::fn_abi_of_instance] for the original documentation.
156+
/// and allows wrapping the actual [FnAbiOf::fn_abi_of_instance_unadjusted] with a tracing span.
157+
/// See [FnAbiOf::fn_abi_of_instance_unadjusted] for the original documentation.
158158
#[inline(always)]
159-
pub fn fn_abi_of_instance(
159+
pub fn fn_abi_of_instance_unadjusted(
160160
&self,
161161
instance: ty::Instance<'tcx>,
162162
extra_args: &'tcx ty::List<Ty<'tcx>>,
163163
) -> <Self as FnAbiOfHelpers<'tcx>>::FnAbiOfResult {
164164
let _trace = enter_trace_span!(M, layouting::fn_abi_of_instance, ?instance, ?extra_args);
165-
FnAbiOf::fn_abi_of_instance(self, instance, extra_args)
165+
FnAbiOf::fn_abi_of_instance_unadjusted(self, instance, extra_args)
166166
}
167167
}
168168

compiler/rustc_const_eval/src/interpret/step.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
476476
let instance = self.resolve(def_id, args)?;
477477
(
478478
FnVal::Instance(instance),
479-
self.fn_abi_of_instance(instance, extra_args)?,
479+
self.fn_abi_of_instance_unadjusted(instance, extra_args)?,
480480
instance.def.requires_caller_location(*self.tcx),
481481
)
482482
}

compiler/rustc_middle/src/query/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,20 @@ rustc_queries! {
18111811
desc { "computing call ABI of `{}` function pointers", key.value.0 }
18121812
}
18131813

1814+
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for
1815+
/// direct calls to an `fn`. This ABI does not have deduced parameter attributes set, and
1816+
/// therefore may produce less optimal codegen than `fn_abi_of_instance` (which should be
1817+
/// preferred instead). However, CTFE may wish to avoid such parameter attribute deduction,
1818+
/// as it can otherwise result in loops during typeck.
1819+
///
1820+
/// NB: that includes virtual calls, which are represented by "direct calls"
1821+
/// to an `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
1822+
query fn_abi_of_instance_unadjusted(
1823+
key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>
1824+
) -> Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> {
1825+
desc { "computing unadjusted call ABI of `{}`", key.value.0 }
1826+
}
1827+
18141828
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for
18151829
/// direct calls to an `fn`.
18161830
///

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,44 @@ pub trait FnAbiOf<'tcx>: FnAbiOfHelpers<'tcx> {
13601360
)
13611361
}
13621362

1363+
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for
1364+
/// direct calls to an `fn`. This ABI does not have deduced parameter attributes set, and
1365+
/// therefore may produce less optimal codegen than `fn_abi_of_instance` (which should be
1366+
/// preferred instead). However, CTFE may wish to avoid such parameter attribute deduction,
1367+
/// as it can otherwise result in loops during typeck.
1368+
///
1369+
/// NB: that includes virtual calls, which are represented by "direct calls"
1370+
/// to an `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
1371+
#[inline]
1372+
#[tracing::instrument(level = "debug", skip(self))]
1373+
fn fn_abi_of_instance_unadjusted(
1374+
&self,
1375+
instance: ty::Instance<'tcx>,
1376+
extra_args: &'tcx ty::List<Ty<'tcx>>,
1377+
) -> Self::FnAbiOfResult {
1378+
// FIXME(eddyb) get a better `span` here.
1379+
let span = self.layout_tcx_at_span();
1380+
let tcx = self.tcx().at(span);
1381+
1382+
MaybeResult::from(
1383+
tcx.fn_abi_of_instance_unadjusted(
1384+
self.typing_env().as_query_input((instance, extra_args)),
1385+
)
1386+
.map_err(|err| {
1387+
// HACK(eddyb) at least for definitions of/calls to `Instance`s,
1388+
// we can get some kind of span even if one wasn't provided.
1389+
// However, we don't do this early in order to avoid calling
1390+
// `def_span` unconditionally (which may have a perf penalty).
1391+
let span = if !span.is_dummy() { span } else { tcx.def_span(instance.def_id()) };
1392+
self.handle_fn_abi_err(
1393+
*err,
1394+
span,
1395+
FnAbiRequest::OfInstance { instance, extra_args },
1396+
)
1397+
}),
1398+
)
1399+
}
1400+
13631401
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for
13641402
/// direct calls to an `fn`.
13651403
///

compiler/rustc_ty_utils/src/abi.rs

Lines changed: 100 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ use rustc_target::callconv::{
2121
use tracing::debug;
2222

2323
pub(crate) fn provide(providers: &mut Providers) {
24-
*providers = Providers { fn_abi_of_fn_ptr, fn_abi_of_instance, ..*providers };
24+
*providers = Providers {
25+
fn_abi_of_fn_ptr,
26+
fn_abi_of_instance_unadjusted,
27+
fn_abi_of_instance,
28+
..*providers
29+
};
2530
}
2631

2732
// NOTE(eddyb) this is private to avoid using it from outside of
@@ -250,25 +255,71 @@ fn fn_abi_of_fn_ptr<'tcx>(
250255
let ty::PseudoCanonicalInput { typing_env, value: (sig, extra_args) } = query;
251256
fn_abi_new_uncached(
252257
&LayoutCx::new(tcx, typing_env),
253-
tcx.instantiate_bound_regions_with_erased(sig),
254-
extra_args,
258+
tcx.normalize_erasing_regions(typing_env, tcx.instantiate_bound_regions_with_erased(sig)),
259+
None,
255260
None,
261+
false,
262+
extra_args,
256263
)
257264
}
258265

259-
fn fn_abi_of_instance<'tcx>(
266+
fn instance_params<'tcx>(
260267
tcx: TyCtxt<'tcx>,
261268
query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
262-
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
269+
) -> (LayoutCx<'tcx>, ty::FnSig<'tcx>, Option<DefId>, Option<Ty<'tcx>>, bool, &'tcx [Ty<'tcx>]) {
263270
let ty::PseudoCanonicalInput { typing_env, value: (instance, extra_args) } = query;
271+
let cx = LayoutCx::new(tcx, typing_env);
272+
let sig =
273+
tcx.normalize_erasing_regions(typing_env, fn_sig_for_fn_abi(tcx, instance, typing_env));
274+
let is_virtual_call = matches!(instance.def, ty::InstanceKind::Virtual(..));
275+
let is_tls_shim_call = matches!(instance.def, ty::InstanceKind::ThreadLocalShim(_));
276+
(
277+
cx,
278+
sig,
279+
(!is_virtual_call && !is_tls_shim_call).then(|| instance.def_id()),
280+
instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty()),
281+
is_virtual_call,
282+
extra_args,
283+
)
284+
}
285+
286+
fn fn_abi_of_instance_unadjusted<'tcx>(
287+
tcx: TyCtxt<'tcx>,
288+
query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
289+
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
290+
let (cx, sig, determined_fn_def_id, caller_location, is_virtual_call, extra_args) =
291+
instance_params(tcx, query);
264292
fn_abi_new_uncached(
265-
&LayoutCx::new(tcx, typing_env),
266-
fn_sig_for_fn_abi(tcx, instance, typing_env),
293+
&cx,
294+
sig,
295+
caller_location,
296+
determined_fn_def_id,
297+
is_virtual_call,
267298
extra_args,
268-
Some(instance),
269299
)
270300
}
271301

302+
fn fn_abi_of_instance<'tcx>(
303+
tcx: TyCtxt<'tcx>,
304+
query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
305+
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
306+
tcx.fn_abi_of_instance_unadjusted(query).map(|fn_abi| {
307+
let (cx, sig, determined_fn_def_id, ..) = instance_params(tcx, query);
308+
determined_fn_def_id.map_or(fn_abi, |determined_fn_def_id| {
309+
fn_abi_adjust_for_deduced_attrs(
310+
&cx,
311+
fn_abi,
312+
sig.abi,
313+
// If this is a virtual call, we cannot pass the `fn_def_id`, as it might call other
314+
// functions from vtable. And for a tls shim, passing the `fn_def_id` would refer to
315+
// the underlying static. Internally, `deduced_param_attrs` attempts to infer attributes
316+
// by visit the function body.
317+
determined_fn_def_id,
318+
)
319+
})
320+
})
321+
}
322+
272323
// Handle safe Rust thin and wide pointers.
273324
fn arg_attrs_for_rust_scalar<'tcx>(
274325
cx: LayoutCx<'tcx>,
@@ -479,27 +530,19 @@ fn fn_abi_sanity_check<'tcx>(
479530
fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret);
480531
}
481532

482-
#[tracing::instrument(level = "debug", skip(cx, instance))]
533+
#[tracing::instrument(
534+
level = "debug",
535+
skip(cx, caller_location, determined_fn_def_id, is_virtual_call)
536+
)]
483537
fn fn_abi_new_uncached<'tcx>(
484538
cx: &LayoutCx<'tcx>,
485539
sig: ty::FnSig<'tcx>,
540+
caller_location: Option<Ty<'tcx>>,
541+
determined_fn_def_id: Option<DefId>,
542+
is_virtual_call: bool,
486543
extra_args: &[Ty<'tcx>],
487-
instance: Option<ty::Instance<'tcx>>,
488544
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
489545
let tcx = cx.tcx();
490-
let (caller_location, determined_fn_def_id, is_virtual_call) = if let Some(instance) = instance
491-
{
492-
let is_virtual_call = matches!(instance.def, ty::InstanceKind::Virtual(..));
493-
let is_tls_shim_call = matches!(instance.def, ty::InstanceKind::ThreadLocalShim(_));
494-
(
495-
instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty()),
496-
if is_virtual_call || is_tls_shim_call { None } else { Some(instance.def_id()) },
497-
is_virtual_call,
498-
)
499-
} else {
500-
(None, None, false)
501-
};
502-
let sig = tcx.normalize_erasing_regions(cx.typing_env, sig);
503546

504547
let abi_map = AbiMap::from_target(&tcx.sess.target);
505548
let conv = abi_map.canonize_abi(sig.abi, sig.c_variadic).unwrap();
@@ -575,16 +618,7 @@ fn fn_abi_new_uncached<'tcx>(
575618
sig.abi,
576619
),
577620
};
578-
fn_abi_adjust_for_abi(
579-
cx,
580-
&mut fn_abi,
581-
sig.abi,
582-
// If this is a virtual call, we cannot pass the `fn_def_id`, as it might call other
583-
// functions from vtable. And for a tls shim, passing the `fn_def_id` would refer to
584-
// the underlying static. Internally, `deduced_param_attrs` attempts to infer attributes
585-
// by visit the function body.
586-
determined_fn_def_id,
587-
);
621+
fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi);
588622
debug!("fn_abi_new_uncached = {:?}", fn_abi);
589623
fn_abi_sanity_check(cx, &fn_abi, sig.abi);
590624
Ok(tcx.arena.alloc(fn_abi))
@@ -595,7 +629,6 @@ fn fn_abi_adjust_for_abi<'tcx>(
595629
cx: &LayoutCx<'tcx>,
596630
fn_abi: &mut FnAbi<'tcx, Ty<'tcx>>,
597631
abi: ExternAbi,
598-
fn_def_id: Option<DefId>,
599632
) {
600633
if abi == ExternAbi::Unadjusted {
601634
// The "unadjusted" ABI passes aggregates in "direct" mode. That's fragile but needed for
@@ -616,30 +649,43 @@ fn fn_abi_adjust_for_abi<'tcx>(
616649
for arg in fn_abi.args.iter_mut() {
617650
unadjust(arg);
618651
}
619-
return;
652+
} else if abi.is_rustic_abi() {
653+
fn_abi.adjust_for_rust_abi(cx);
654+
} else {
655+
fn_abi.adjust_for_foreign_abi(cx, abi);
620656
}
657+
}
621658

659+
#[tracing::instrument(level = "trace", skip(cx))]
660+
fn fn_abi_adjust_for_deduced_attrs<'tcx>(
661+
cx: &LayoutCx<'tcx>,
662+
fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>,
663+
abi: ExternAbi,
664+
fn_def_id: DefId,
665+
) -> &'tcx FnAbi<'tcx, Ty<'tcx>> {
622666
let tcx = cx.tcx();
623-
624-
if abi.is_rustic_abi() {
625-
fn_abi.adjust_for_rust_abi(cx);
626-
// Look up the deduced parameter attributes for this function, if we have its def ID and
627-
// we're optimizing in non-incremental mode. We'll tag its parameters with those attributes
628-
// as appropriate.
629-
let deduced =
630-
if tcx.sess.opts.optimize != OptLevel::No && tcx.sess.opts.incremental.is_none() {
631-
fn_def_id.map(|fn_def_id| tcx.deduced_param_attrs(fn_def_id)).unwrap_or_default()
632-
} else {
633-
&[]
634-
};
635-
if !deduced.is_empty() {
636-
apply_deduced_attributes(cx, deduced, 0, &mut fn_abi.ret);
637-
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
638-
apply_deduced_attributes(cx, deduced, arg_idx + 1, arg);
639-
}
640-
}
667+
// Look up the deduced parameter attributes for this function, if we have its def ID and
668+
// we're optimizing in non-incremental mode. We'll tag its parameters with those attributes
669+
// as appropriate.
670+
let deduced = if abi.is_rustic_abi()
671+
&& tcx.sess.opts.optimize != OptLevel::No
672+
&& tcx.sess.opts.incremental.is_none()
673+
{
674+
tcx.deduced_param_attrs(fn_def_id)
641675
} else {
642-
fn_abi.adjust_for_foreign_abi(cx, abi);
676+
&[]
677+
};
678+
if deduced.is_empty() {
679+
fn_abi
680+
} else {
681+
let mut fn_abi = fn_abi.clone();
682+
apply_deduced_attributes(cx, deduced, 0, &mut fn_abi.ret);
683+
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
684+
apply_deduced_attributes(cx, deduced, arg_idx + 1, arg);
685+
}
686+
debug!("fn_abi_adjust_for_deduced_attrs = {:?}", fn_abi);
687+
fn_abi_sanity_check(cx, &fn_abi, abi);
688+
tcx.arena.alloc(fn_abi)
643689
}
644690
}
645691

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//! Items whose type depends on CTFE (such as the async closure/coroutine beneath, whose type
2+
//! depends upon evaluating `do_nothing`) should not cause a query cycle owing to the deduction of
3+
//! the function's parameter attributes, which are only required for codegen and not for CTFE.
4+
//!
5+
//! Regression test for https://github.com/rust-lang/rust/issues/151748
6+
//@ compile-flags: -O
7+
//@ edition: 2018
8+
//@ check-pass
9+
10+
fn main() {
11+
let _ = async || {
12+
let COMPLEX_CONSTANT = ();
13+
};
14+
}
15+
16+
const fn do_nothing() {}
17+
18+
const COMPLEX_CONSTANT: () = do_nothing();

0 commit comments

Comments
 (0)