Skip to content

Commit 132a633

Browse files
committed
Do not deduce parameter attributes during CTFE
`fn_abi_of_instance` can look at function bodies to deduce codegen optimization attributes (namely `ArgAttribute::ReadOnly` and `ArgAttribute::CapturesNone`) of indirectly-passed parameters. This can lead to cycles when performed during typeck, when such attributes are irrelevant. This patch breaks a subquery out from `fn_abi_of_instance`, `fn_abi_of_instance_no_deduced_attrs`, which returns the ABI before such parameter attributes are deduced; and that new subquery is used in CTFE instead.
1 parent ba284f4 commit 132a633

7 files changed

Lines changed: 200 additions & 69 deletions

File tree

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ 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 =
355+
self.fn_abi_of_instance_no_deduced_attrs(instance, ty::List::empty())?;
355356

356357
if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
357358
throw_unsup_format!("calling a c-variadic function is not supported");
@@ -839,7 +840,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
839840
enter_trace_span!(M, resolve::resolve_drop_in_place, ty = ?place.layout.ty);
840841
ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty)
841842
};
842-
let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?;
843+
let fn_abi = self.fn_abi_of_instance_no_deduced_attrs(instance, ty::List::empty())?;
843844

844845
let arg = self.mplace_to_ref(&place)?;
845846
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_no_deduced_attrs] with a tracing span.
157+
/// See [FnAbiOf::fn_abi_of_instance_no_deduced_attrs] for the original documentation.
158158
#[inline(always)]
159-
pub fn fn_abi_of_instance(
159+
pub fn fn_abi_of_instance_no_deduced_attrs(
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_no_deduced_attrs(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_no_deduced_attrs(instance, extra_args)?,
480480
instance.def.requires_caller_location(*self.tcx),
481481
)
482482
}

compiler/rustc_middle/src/query/mod.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,11 +1811,32 @@ 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`.
1814+
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls*
1815+
/// to an `fn`. Indirectly-passed parameters in the returned ABI might not include all possible
1816+
/// codegen optimization attributes (such as `ReadOnly` or `CapturesNone`), as deducing these
1817+
/// requires inspection of function bodies that can lead to cycles when performed during typeck.
1818+
/// Post typeck, you should prefer the optimized ABI returned by `fn_abi_of_instance`.
1819+
///
1820+
/// NB: the ABI returned by this query must not differ from that returned by
1821+
/// `fn_abi_of_instance` in any other way.
1822+
///
1823+
/// * that includes virtual calls, which are represented by "direct calls" to an
1824+
/// `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
1825+
query fn_abi_of_instance_no_deduced_attrs(
1826+
key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>
1827+
) -> Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> {
1828+
desc { "computing unadjusted call ABI of `{}`", key.value.0 }
1829+
}
1830+
1831+
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls*
1832+
/// to an `fn`. Indirectly-passed parameters in the returned ABI will include applicable
1833+
/// codegen optimization attributes, including `ReadOnly` and `CapturesNone` -- deduction of
1834+
/// which requires inspection of function bodies that can lead to cycles when performed during
1835+
/// typeck. During typeck, you should therefore use instead the unoptimized ABI returned by
1836+
/// `fn_abi_of_instance_no_deduced_attrs`.
18161837
///
1817-
/// NB: that includes virtual calls, which are represented by "direct calls"
1818-
/// to an `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
1838+
/// * that includes virtual calls, which are represented by "direct calls" to an
1839+
/// `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
18191840
query fn_abi_of_instance(
18201841
key: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>
18211842
) -> Result<&'tcx rustc_target::callconv::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> {

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,11 +1360,56 @@ 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`.
1363+
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls*
1364+
/// to an `fn`. Indirectly-passed parameters in the returned ABI might not include all possible
1365+
/// codegen optimization attributes (such as `ReadOnly` or `CapturesNone`), as deducing these
1366+
/// requires inspection of function bodies that can lead to cycles when performed during typeck.
1367+
/// Post typeck, you should prefer the optimized ABI returned by `fn_abi_of_instance`.
13651368
///
1366-
/// NB: that includes virtual calls, which are represented by "direct calls"
1367-
/// to an `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
1369+
/// NB: the ABI returned by this query must not differ from that returned by
1370+
/// `fn_abi_of_instance` in any other way.
1371+
///
1372+
/// * that includes virtual calls, which are represented by "direct calls" to an
1373+
/// `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
1374+
#[inline]
1375+
#[tracing::instrument(level = "debug", skip(self))]
1376+
fn fn_abi_of_instance_no_deduced_attrs(
1377+
&self,
1378+
instance: ty::Instance<'tcx>,
1379+
extra_args: &'tcx ty::List<Ty<'tcx>>,
1380+
) -> Self::FnAbiOfResult {
1381+
// FIXME(eddyb) get a better `span` here.
1382+
let span = self.layout_tcx_at_span();
1383+
let tcx = self.tcx().at(span);
1384+
1385+
MaybeResult::from(
1386+
tcx.fn_abi_of_instance_no_deduced_attrs(
1387+
self.typing_env().as_query_input((instance, extra_args)),
1388+
)
1389+
.map_err(|err| {
1390+
// HACK(eddyb) at least for definitions of/calls to `Instance`s,
1391+
// we can get some kind of span even if one wasn't provided.
1392+
// However, we don't do this early in order to avoid calling
1393+
// `def_span` unconditionally (which may have a perf penalty).
1394+
let span = if !span.is_dummy() { span } else { tcx.def_span(instance.def_id()) };
1395+
self.handle_fn_abi_err(
1396+
*err,
1397+
span,
1398+
FnAbiRequest::OfInstance { instance, extra_args },
1399+
)
1400+
}),
1401+
)
1402+
}
1403+
1404+
/// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls*
1405+
/// to an `fn`. Indirectly-passed parameters in the returned ABI will include applicable
1406+
/// codegen optimization attributes, including `ReadOnly` and `CapturesNone` -- deduction of
1407+
/// which requires inspection of function bodies that can lead to cycles when performed during
1408+
/// typeck. During typeck, you should therefore use instead the unoptimized ABI returned by
1409+
/// `fn_abi_of_instance_no_deduced_attrs`.
1410+
///
1411+
/// * that includes virtual calls, which are represented by "direct calls" to an
1412+
/// `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
13681413
#[inline]
13691414
#[tracing::instrument(level = "debug", skip(self))]
13701415
fn fn_abi_of_instance(

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_no_deduced_attrs,
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_no_deduced_attrs<'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_no_deduced_attrs(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)