Skip to content

Commit e6059f5

Browse files
committed
Auto merge of rust-lang#137669 - DianQK:fn-atts-virtual, r=saethlin
Don't infer attributes of virtual calls based on the function body Fixes (after backport) rust-lang#137646. Since we don't know the exact implementation of the virtual call, it might write to parameters, we can't infer the readonly attribute.
2 parents f45d4ac + fbe0075 commit e6059f5

File tree

4 files changed

+123
-38
lines changed

4 files changed

+123
-38
lines changed

Diff for: compiler/rustc_middle/src/ty/instance.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ pub enum InstanceKind<'tcx> {
111111

112112
/// Dynamic dispatch to `<dyn Trait as Trait>::fn`.
113113
///
114-
/// This `InstanceKind` does not have callable MIR. Calls to `Virtual` instances must be
115-
/// codegen'd as virtual calls through the vtable.
114+
/// This `InstanceKind` may have a callable MIR as the default implementation.
115+
/// Calls to `Virtual` instances must be codegen'd as virtual calls through the vtable.
116+
/// *This means we might not know exactly what is being called.*
116117
///
117118
/// If this is reified to a `fn` pointer, a `ReifyShim` is used (see `ReifyShim` above for more
118119
/// details on that).

Diff for: compiler/rustc_ty_utils/src/abi.rs

+38-36
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,11 @@ fn fn_abi_of_fn_ptr<'tcx>(
309309
query: ty::PseudoCanonicalInput<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
310310
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
311311
let ty::PseudoCanonicalInput { typing_env, value: (sig, extra_args) } = query;
312-
313-
let cx = LayoutCx::new(tcx, typing_env);
314312
fn_abi_new_uncached(
315-
&cx,
313+
&LayoutCx::new(tcx, typing_env),
316314
tcx.instantiate_bound_regions_with_erased(sig),
317315
extra_args,
318316
None,
319-
None,
320-
false,
321317
)
322318
}
323319

@@ -326,19 +322,11 @@ fn fn_abi_of_instance<'tcx>(
326322
query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
327323
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
328324
let ty::PseudoCanonicalInput { typing_env, value: (instance, extra_args) } = query;
329-
330-
let sig = fn_sig_for_fn_abi(tcx, instance, typing_env);
331-
332-
let caller_location =
333-
instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty());
334-
335325
fn_abi_new_uncached(
336326
&LayoutCx::new(tcx, typing_env),
337-
sig,
327+
fn_sig_for_fn_abi(tcx, instance, typing_env),
338328
extra_args,
339-
caller_location,
340-
Some(instance.def_id()),
341-
matches!(instance.def, ty::InstanceKind::Virtual(..)),
329+
Some(instance),
342330
)
343331
}
344332

@@ -547,19 +535,25 @@ fn fn_abi_sanity_check<'tcx>(
547535
fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret);
548536
}
549537

550-
// FIXME(eddyb) perhaps group the signature/type-containing (or all of them?)
551-
// arguments of this method, into a separate `struct`.
552-
#[tracing::instrument(level = "debug", skip(cx, caller_location, fn_def_id, force_thin_self_ptr))]
538+
#[tracing::instrument(level = "debug", skip(cx, instance))]
553539
fn fn_abi_new_uncached<'tcx>(
554540
cx: &LayoutCx<'tcx>,
555541
sig: ty::FnSig<'tcx>,
556542
extra_args: &[Ty<'tcx>],
557-
caller_location: Option<Ty<'tcx>>,
558-
fn_def_id: Option<DefId>,
559-
// FIXME(eddyb) replace this with something typed, like an `enum`.
560-
force_thin_self_ptr: bool,
543+
instance: Option<ty::Instance<'tcx>>,
561544
) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
562545
let tcx = cx.tcx();
546+
let (caller_location, determined_fn_def_id, is_virtual_call) = if let Some(instance) = instance
547+
{
548+
let is_virtual_call = matches!(instance.def, ty::InstanceKind::Virtual(..));
549+
(
550+
instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty()),
551+
if is_virtual_call { None } else { Some(instance.def_id()) },
552+
is_virtual_call,
553+
)
554+
} else {
555+
(None, None, false)
556+
};
563557
let sig = tcx.normalize_erasing_regions(cx.typing_env, sig);
564558

565559
let conv = conv_from_spec_abi(cx.tcx(), sig.abi, sig.c_variadic);
@@ -568,16 +562,11 @@ fn fn_abi_new_uncached<'tcx>(
568562
let extra_args = if sig.abi == ExternAbi::RustCall {
569563
assert!(!sig.c_variadic && extra_args.is_empty());
570564

571-
if let Some(input) = sig.inputs().last() {
572-
if let ty::Tuple(tupled_arguments) = input.kind() {
573-
inputs = &sig.inputs()[0..sig.inputs().len() - 1];
574-
tupled_arguments
575-
} else {
576-
bug!(
577-
"argument to function with \"rust-call\" ABI \
578-
is not a tuple"
579-
);
580-
}
565+
if let Some(input) = sig.inputs().last()
566+
&& let ty::Tuple(tupled_arguments) = input.kind()
567+
{
568+
inputs = &sig.inputs()[0..sig.inputs().len() - 1];
569+
tupled_arguments
581570
} else {
582571
bug!(
583572
"argument to function with \"rust-call\" ABI \
@@ -590,7 +579,7 @@ fn fn_abi_new_uncached<'tcx>(
590579
};
591580

592581
let is_drop_in_place =
593-
fn_def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::DropInPlace));
582+
determined_fn_def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::DropInPlace));
594583

595584
let arg_of = |ty: Ty<'tcx>, arg_idx: Option<usize>| -> Result<_, &'tcx FnAbiError<'tcx>> {
596585
let span = tracing::debug_span!("arg_of");
@@ -603,7 +592,7 @@ fn fn_abi_new_uncached<'tcx>(
603592
});
604593

605594
let layout = cx.layout_of(ty).map_err(|err| &*tcx.arena.alloc(FnAbiError::Layout(*err)))?;
606-
let layout = if force_thin_self_ptr && arg_idx == Some(0) {
595+
let layout = if is_virtual_call && arg_idx == Some(0) {
607596
// Don't pass the vtable, it's not an argument of the virtual fn.
608597
// Instead, pass just the data pointer, but give it the type `*const/mut dyn Trait`
609598
// or `&/&mut dyn Trait` because this is special-cased elsewhere in codegen
@@ -646,9 +635,22 @@ fn fn_abi_new_uncached<'tcx>(
646635
c_variadic: sig.c_variadic,
647636
fixed_count: inputs.len() as u32,
648637
conv,
649-
can_unwind: fn_can_unwind(cx.tcx(), fn_def_id, sig.abi),
638+
can_unwind: fn_can_unwind(
639+
tcx,
640+
// Since `#[rustc_nounwind]` can change unwinding, we cannot infer unwinding by `fn_def_id` for a virtual call.
641+
determined_fn_def_id,
642+
sig.abi,
643+
),
650644
};
651-
fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi, fn_def_id);
645+
fn_abi_adjust_for_abi(
646+
cx,
647+
&mut fn_abi,
648+
sig.abi,
649+
// If this is a virtual call, we cannot pass the `fn_def_id`, as it might call other
650+
// functions from vtable. Internally, `deduced_param_attrs` attempts to infer attributes by
651+
// visit the function body.
652+
determined_fn_def_id,
653+
);
652654
debug!("fn_abi_new_uncached = {:?}", fn_abi);
653655
fn_abi_sanity_check(cx, &fn_abi, sig.abi);
654656
Ok(tcx.arena.alloc(fn_abi))

Diff for: tests/codegen/virtual-call-attrs-issue-137646.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Regression test for https://github.com/rust-lang/rust/issues/137646.
2+
//! Since we don't know the exact implementation of the virtual call,
3+
//! it might write to parameters, we can't infer the readonly attribute.
4+
//@ compile-flags: -C opt-level=3 -C no-prepopulate-passes
5+
6+
#![crate_type = "lib"]
7+
#![feature(rustc_attrs)]
8+
9+
pub trait Trait {
10+
#[rustc_nounwind]
11+
fn m(&self, _: (i32, i32, i32)) {}
12+
}
13+
14+
#[no_mangle]
15+
pub fn foo(trait_: &dyn Trait) {
16+
// CHECK-LABEL: @foo(
17+
// CHECK: call void
18+
// CHECK-NOT: readonly
19+
trait_.m((1, 1, 1));
20+
}
21+
22+
#[no_mangle]
23+
#[rustc_nounwind]
24+
pub fn foo_nounwind(trait_: &dyn Trait) {
25+
// CHECK-LABEL: @foo_nounwind(
26+
// FIXME: Here should be invoke.
27+
// COM: CHECK: invoke
28+
trait_.m((1, 1, 1));
29+
}
30+
31+
#[no_mangle]
32+
pub extern "C" fn c_nounwind(trait_: &dyn Trait) {
33+
// CHECK-LABEL: @c_nounwind(
34+
// FIXME: Here should be invoke.
35+
// COM: CHECK: invoke
36+
trait_.m((1, 1, 1));
37+
}

Diff for: tests/ui/virtual-call-attrs-issue-137646.rs

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! Regression test for https://github.com/rust-lang/rust/issues/137646.
2+
//! The parameter value at all calls to `check` should be `(1, 1, 1)`.
3+
4+
//@ run-pass
5+
6+
use std::hint::black_box;
7+
8+
type T = (i32, i32, i32);
9+
10+
pub trait Trait {
11+
fn m(&self, _: T, _: T) {}
12+
}
13+
14+
impl Trait for () {
15+
fn m(&self, mut _v1: T, v2: T) {
16+
_v1 = (0, 0, 0);
17+
check(v2);
18+
}
19+
}
20+
21+
pub fn run_1(trait_: &dyn Trait) {
22+
let v1 = (1, 1, 1);
23+
let v2 = (1, 1, 1);
24+
trait_.m(v1, v2);
25+
}
26+
27+
pub fn run_2(trait_: &dyn Trait) {
28+
let v1 = (1, 1, 1);
29+
let v2 = (1, 1, 1);
30+
trait_.m(v1, v2);
31+
check(v1);
32+
check(v2);
33+
}
34+
35+
#[inline(never)]
36+
fn check(v: T) {
37+
assert_eq!(v, (1, 1, 1));
38+
}
39+
40+
fn main() {
41+
black_box(run_1 as fn(&dyn Trait));
42+
black_box(run_2 as fn(&dyn Trait));
43+
run_1(&());
44+
run_2(&());
45+
}

0 commit comments

Comments
 (0)