Auto merge of #143290 - azhogin:azhogin/link-pub-async-impls, r=oli-obk

pub async fn impl is monomorphized when func itself is monomorphized

Implentation coroutine (`func::{closure#0}`) is monomorphized, when func itself is monomorphized.

Currently, when `pub async fn foo(..)` is exported from lib and used in several dependent crates, only 'header' function is monomorphized in the defining crate. 'header' function, returning coroutine object, is monomorphized, but the coroutine's poll function (which actually implements all the logic for the function) is not. In such situation, `func::{closure#0}` will be monomorphized in every dependency.

This PR adds monomorphization for `func::{closure#0}` (coroutine poll function), when func itself is monomorphized.

Simple test with one lib async function and ten dependent crates (executable) that use the function, shows 5-7% compilation time improvement (single-threaded).
This commit is contained in:
bors
2025-09-01 10:54:40 +00:00
13 changed files with 245 additions and 15 deletions
+14 -1
View File
@@ -1535,7 +1535,20 @@ fn process_impl_item(&mut self, id: hir::ImplItemId) {
fn process_nested_body(&mut self, def_id: LocalDefId) {
match self.tcx.def_kind(def_id) {
DefKind::Closure => {
if self.strategy == MonoItemCollectionStrategy::Eager
// for 'pub async fn foo(..)' also trying to monomorphize foo::{closure}
let is_pub_fn_coroutine =
match *self.tcx.type_of(def_id).instantiate_identity().kind() {
ty::Coroutine(cor_id, _args) => {
let tcx = self.tcx;
let parent_id = tcx.parent(cor_id);
tcx.def_kind(parent_id) == DefKind::Fn
&& tcx.asyncness(parent_id).is_async()
&& tcx.visibility(parent_id).is_public()
}
ty::Closure(..) | ty::CoroutineClosure(..) => false,
_ => unreachable!(),
};
if (self.strategy == MonoItemCollectionStrategy::Eager || is_pub_fn_coroutine)
&& !self
.tcx
.generics_of(self.tcx.typeck_root_def_id(def_id.to_def_id()))
@@ -18,11 +18,11 @@ pub async fn async_fn_test() {
pub async fn foo() {}
// NONMSVC: [[AWAITEE_TYPE:![0-9]*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "{async_fn_env#0}", scope: [[AWAITEE_SCOPE:![0-9]*]],
// MSVC: [[AWAITEE_TYPE:![0-9]*]] = !DICompositeType(tag: DW_TAG_union_type, name: "enum2$<async_fn_debug_awaitee_field::foo::async_fn_env$0>",
// NONMSVC: [[AWAITEE_SCOPE]] = !DINamespace(name: "foo",
// NONMSVC: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "{async_fn_env#0}", scope: [[GEN_SCOPE:![0-9]*]],
// MSVC: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_union_type, name: "enum2$<async_fn_debug_awaitee_field::async_fn_test::async_fn_env$0>",
// NONMSVC: [[GEN_SCOPE:!.*]] = !DINamespace(name: "async_fn_test",
// CHECK: [[SUSPEND_STRUCT:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Suspend0", scope: [[GEN]],
// CHECK: !DIDerivedType(tag: DW_TAG_member, name: "__awaitee", scope: [[SUSPEND_STRUCT]], {{.*}}, baseType: [[AWAITEE_TYPE:![0-9]*]],
// NONMSVC: [[AWAITEE_TYPE]] = !DICompositeType(tag: DW_TAG_structure_type, name: "{async_fn_env#0}", scope: [[AWAITEE_SCOPE:![0-9]*]],
// MSVC: [[AWAITEE_TYPE]] = !DICompositeType(tag: DW_TAG_union_type, name: "enum2$<async_fn_debug_awaitee_field::foo::async_fn_env$0>",
// NONMSVC: [[AWAITEE_SCOPE]] = !DINamespace(name: "foo",
// CHECK: !DIDerivedType(tag: DW_TAG_member, name: "__awaitee", scope: [[SUSPEND_STRUCT]], {{.*}}, baseType: [[AWAITEE_TYPE]],
@@ -0,0 +1,10 @@
//@ edition: 2024
// When pub async fn is monomorphized, its implementation coroutine is also monomorphized
//@ compile-flags: --crate-type=lib
//~ MONO_ITEM fn async_fn @@
//~ MONO_ITEM fn async_fn::{closure#0} @@
#[unsafe(no_mangle)]
pub async fn async_fn(x: u64) -> bool {
true
}
@@ -0,0 +1,89 @@
//@ only-x86_64-unknown-linux-gnu
//@ compile-flags: -C panic=abort -Zinline-mir=no -Copt-level=0 -Zcross-crate-inline-threshold=never -Zmir-opt-level=0 -Cno-prepopulate-passes
//@ no-prefer-dynamic
//@ edition:2024
#![crate_type = "lib"]
trait TestTrait {
fn test_func(&self);
}
struct TestStruct {}
impl TestTrait for TestStruct {
fn test_func(&self) {
println!("TestStruct::test_func");
}
}
#[inline(never)]
pub fn foo() -> impl TestTrait {
TestStruct {}
}
//~ MONO_ITEM fn foo
//~ MONO_ITEM fn <TestStruct as TestTrait>::test_func
trait TestTrait2 {
fn test_func2(&self);
}
struct TestStruct2 {}
impl TestTrait2 for TestStruct2 {
fn test_func2(&self) {
println!("TestStruct2::test_func2");
}
}
#[inline(never)]
pub fn foo2() -> Box<dyn TestTrait2> {
Box::new(TestStruct2 {})
}
//~ MONO_ITEM fn <TestStruct2 as TestTrait2>::test_func2
//~ MONO_ITEM fn alloc::alloc::exchange_malloc
//~ MONO_ITEM fn foo2
//~ MONO_ITEM fn std::alloc::Global::alloc_impl
//~ MONO_ITEM fn std::boxed::Box::<TestStruct2>::new
//~ MONO_ITEM fn std::alloc::Layout::from_size_align_unchecked::precondition_check
//~ MONO_ITEM fn std::ptr::NonNull::<T>::new_unchecked::precondition_check
struct Counter {
count: usize,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 { Some(self.count) } else { None }
}
}
#[inline(never)]
pub fn foo3() -> Box<dyn Iterator<Item = usize>> {
Box::new(Counter::new())
}
//~ MONO_ITEM fn <Counter as std::iter::Iterator::advance_by::SpecAdvanceBy>::spec_advance_by
//~ MONO_ITEM fn <Counter as std::iter::Iterator::advance_by::SpecAdvanceBy>::spec_advance_by::{closure#0}
//~ MONO_ITEM fn <Counter as std::iter::Iterator>::advance_by
//~ MONO_ITEM fn <Counter as std::iter::Iterator>::next
//~ MONO_ITEM fn <Counter as std::iter::Iterator>::nth
//~ MONO_ITEM fn <Counter as std::iter::Iterator>::size_hint
//~ MONO_ITEM fn <Counter as std::iter::Iterator>::try_fold::<std::num::NonZero<usize>, {closure@<Counter as std::iter::Iterator::advance_by::SpecAdvanceBy>::spec_advance_by::{closure#0}}, std::option::Option<std::num::NonZero<usize>>>
//~ MONO_ITEM fn <std::option::Option<std::num::NonZero<usize>> as std::ops::FromResidual<std::option::Option<std::convert::Infallible>>>::from_residual
//~ MONO_ITEM fn <std::option::Option<std::num::NonZero<usize>> as std::ops::Try>::branch
//~ MONO_ITEM fn <std::option::Option<std::num::NonZero<usize>> as std::ops::Try>::from_output
//~ MONO_ITEM fn foo3
//~ MONO_ITEM fn std::boxed::Box::<Counter>::new
//~ MONO_ITEM fn Counter::new
//~ MONO_ITEM fn core::fmt::rt::<impl std::fmt::Arguments<'_>>::new_const::<1>
+4 -4
View File
@@ -103,21 +103,21 @@ Number of file 0 mappings: 3
Highest counter ID seen: (none)
Function name: async::g
Raw bytes (9): 0x[01, 01, 00, 01, 01, 1b, 01, 00, 16]
Raw bytes (9): 0x[01, 01, 00, 01, 01, 1b, 01, 00, 12]
Number of files: 1
- file 0 => $DIR/async.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 27, 1) to (start + 0, 22)
- Code(Counter(0)) at (prev + 27, 1) to (start + 0, 18)
Highest counter ID seen: c0
Function name: async::g::{closure#0} (unused)
Raw bytes (64): 0x[01, 01, 00, 0c, 00, 1b, 17, 00, 18, 00, 01, 0b, 00, 0c, 00, 01, 09, 00, 0a, 00, 00, 0e, 00, 17, 00, 00, 1b, 00, 1c, 00, 00, 20, 00, 22, 00, 01, 09, 00, 0a, 00, 00, 0e, 00, 17, 00, 00, 1b, 00, 1c, 00, 00, 20, 00, 22, 00, 01, 0e, 00, 10, 00, 02, 01, 00, 02]
Raw bytes (64): 0x[01, 01, 00, 0c, 00, 1b, 13, 00, 14, 00, 01, 0b, 00, 0c, 00, 01, 09, 00, 0a, 00, 00, 0e, 00, 17, 00, 00, 1b, 00, 1c, 00, 00, 20, 00, 22, 00, 01, 09, 00, 0a, 00, 00, 0e, 00, 17, 00, 00, 1b, 00, 1c, 00, 00, 20, 00, 22, 00, 01, 0e, 00, 10, 00, 02, 01, 00, 02]
Number of files: 1
- file 0 => $DIR/async.rs
Number of expressions: 0
Number of file 0 mappings: 12
- Code(Zero) at (prev + 27, 23) to (start + 0, 24)
- Code(Zero) at (prev + 27, 19) to (start + 0, 20)
- Code(Zero) at (prev + 1, 11) to (start + 0, 12)
- Code(Zero) at (prev + 1, 9) to (start + 0, 10)
- Code(Zero) at (prev + 0, 14) to (start + 0, 23)
+2 -2
View File
@@ -24,8 +24,8 @@
LL| |
LL| 0|async fn foo() -> [bool; 10] { [false; 10] } // unused function; executor does not block on `h()`
LL| |
LL| 1|pub async fn g(x: u8) {
^0
LL| 1|async fn g(x: u8) {
^0
LL| 0| match x {
LL| 0| y if e().await == y => (),
LL| 0| y if f().await == y => (),
+1 -1
View File
@@ -24,7 +24,7 @@ async fn f() -> u8 { 1 }
async fn foo() -> [bool; 10] { [false; 10] } // unused function; executor does not block on `h()`
pub async fn g(x: u8) {
async fn g(x: u8) {
match x {
y if e().await == y => (),
y if f().await == y => (),
@@ -1,7 +1,9 @@
//@ compile-flags: -Z print-type-sizes --crate-type lib
//@ compile-flags: -C panic=abort -Z print-type-sizes --crate-type lib
//@ needs-deterministic-layouts
//@ edition:2021
//@ build-pass
//@ ignore-pass
//@ only-x86_64
async fn wait() {}
@@ -48,6 +48,39 @@ print-type-size variant `Returned`: 1024 bytes
print-type-size upvar `.arg`: 1024 bytes
print-type-size variant `Panicked`: 1024 bytes
print-type-size upvar `.arg`: 1024 bytes
print-type-size type: `std::task::Context<'_>`: 32 bytes, alignment: 8 bytes
print-type-size field `.waker`: 8 bytes
print-type-size field `.local_waker`: 8 bytes
print-type-size field `.ext`: 16 bytes
print-type-size field `._marker`: 0 bytes
print-type-size field `._marker2`: 0 bytes
print-type-size type: `std::panic::Location<'_>`: 24 bytes, alignment: 8 bytes
print-type-size field `.filename`: 16 bytes
print-type-size field `.line`: 4 bytes
print-type-size field `.col`: 4 bytes
print-type-size field `._filename`: 0 bytes
print-type-size type: `core::task::wake::ExtData<'_>`: 16 bytes, alignment: 8 bytes
print-type-size variant `Some`: 16 bytes
print-type-size field `.0`: 16 bytes
print-type-size variant `None`: 0 bytes
print-type-size field `.0`: 0 bytes
print-type-size type: `std::panic::AssertUnwindSafe<core::task::wake::ExtData<'_>>`: 16 bytes, alignment: 8 bytes
print-type-size field `.0`: 16 bytes
print-type-size type: `std::ptr::NonNull<str>`: 16 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 16 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of big_fut()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of calls_fut<{async fn body of big_fut()}>()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of test()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of wait()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::ptr::DynMetadata<dyn std::any::Any>`: 8 bytes, alignment: 8 bytes
print-type-size field `._vtable_ptr`: 8 bytes
print-type-size field `._phantom`: 0 bytes
print-type-size type: `std::ptr::NonNull<std::ptr::metadata::VTable>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::mem::ManuallyDrop<bool>`: 1 bytes, alignment: 1 bytes
print-type-size field `.value`: 1 bytes
print-type-size type: `std::mem::ManuallyDrop<{async fn body of wait()}>`: 1 bytes, alignment: 1 bytes
@@ -70,3 +103,7 @@ print-type-size discriminant: 1 bytes
print-type-size variant `Unresumed`: 0 bytes
print-type-size variant `Returned`: 0 bytes
print-type-size variant `Panicked`: 0 bytes
print-type-size type: `std::marker::PhantomData<&str>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<*mut ()>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<dyn std::any::Any>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<fn(&()) -> &()>`: 0 bytes, alignment: 1 bytes
@@ -1,7 +1,9 @@
//@ compile-flags: -Z print-type-sizes --crate-type=lib
//@ compile-flags: -C panic=abort -Z print-type-sizes --crate-type=lib
//@ needs-deterministic-layouts
//@ edition: 2021
//@ build-pass
//@ ignore-pass
//@ only-x86_64
pub async fn test() {
let _ = a([0u8; 1024]).await;
@@ -58,3 +58,45 @@ print-type-size variant `Returned`: 1024 bytes
print-type-size upvar `.t`: 1024 bytes
print-type-size variant `Panicked`: 1024 bytes
print-type-size upvar `.t`: 1024 bytes
print-type-size type: `std::task::Context<'_>`: 32 bytes, alignment: 8 bytes
print-type-size field `.waker`: 8 bytes
print-type-size field `.local_waker`: 8 bytes
print-type-size field `.ext`: 16 bytes
print-type-size field `._marker`: 0 bytes
print-type-size field `._marker2`: 0 bytes
print-type-size type: `std::panic::Location<'_>`: 24 bytes, alignment: 8 bytes
print-type-size field `.filename`: 16 bytes
print-type-size field `.line`: 4 bytes
print-type-size field `.col`: 4 bytes
print-type-size field `._filename`: 0 bytes
print-type-size type: `core::task::wake::ExtData<'_>`: 16 bytes, alignment: 8 bytes
print-type-size variant `Some`: 16 bytes
print-type-size field `.0`: 16 bytes
print-type-size variant `None`: 0 bytes
print-type-size field `.0`: 0 bytes
print-type-size type: `std::panic::AssertUnwindSafe<core::task::wake::ExtData<'_>>`: 16 bytes, alignment: 8 bytes
print-type-size field `.0`: 16 bytes
print-type-size type: `std::ptr::NonNull<str>`: 16 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 16 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of a<[u8; 1024]>()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of b<[u8; 1024]>()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of c<[u8; 1024]>()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of test()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::ptr::DynMetadata<dyn std::any::Any>`: 8 bytes, alignment: 8 bytes
print-type-size field `._vtable_ptr`: 8 bytes
print-type-size field `._phantom`: 0 bytes
print-type-size type: `std::ptr::NonNull<std::ptr::metadata::VTable>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::task::Poll<()>`: 1 bytes, alignment: 1 bytes
print-type-size discriminant: 1 bytes
print-type-size variant `Ready`: 0 bytes
print-type-size field `.0`: 0 bytes
print-type-size variant `Pending`: 0 bytes
print-type-size type: `std::marker::PhantomData<&str>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<*mut ()>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<dyn std::any::Any>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<fn(&()) -> &()>`: 0 bytes, alignment: 1 bytes
+3 -1
View File
@@ -1,7 +1,9 @@
//@ compile-flags: -Z print-type-sizes --crate-type lib
//@ compile-flags: -C panic=abort -Z print-type-sizes --crate-type lib
//@ needs-deterministic-layouts
//@ edition:2021
//@ build-pass
//@ ignore-pass
//@ only-x86_64
#![allow(dropping_copy_types)]
+33
View File
@@ -16,6 +16,35 @@ print-type-size type: `std::mem::MaybeUninit<[u8; 8192]>`: 8192 bytes, alignment
print-type-size variant `MaybeUninit`: 8192 bytes
print-type-size field `.uninit`: 0 bytes
print-type-size field `.value`: 8192 bytes
print-type-size type: `std::task::Context<'_>`: 32 bytes, alignment: 8 bytes
print-type-size field `.waker`: 8 bytes
print-type-size field `.local_waker`: 8 bytes
print-type-size field `.ext`: 16 bytes
print-type-size field `._marker`: 0 bytes
print-type-size field `._marker2`: 0 bytes
print-type-size type: `std::panic::Location<'_>`: 24 bytes, alignment: 8 bytes
print-type-size field `.filename`: 16 bytes
print-type-size field `.line`: 4 bytes
print-type-size field `.col`: 4 bytes
print-type-size field `._filename`: 0 bytes
print-type-size type: `core::task::wake::ExtData<'_>`: 16 bytes, alignment: 8 bytes
print-type-size variant `Some`: 16 bytes
print-type-size field `.0`: 16 bytes
print-type-size variant `None`: 0 bytes
print-type-size field `.0`: 0 bytes
print-type-size type: `std::panic::AssertUnwindSafe<core::task::wake::ExtData<'_>>`: 16 bytes, alignment: 8 bytes
print-type-size field `.0`: 16 bytes
print-type-size type: `std::ptr::NonNull<str>`: 16 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 16 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of test()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::pin::Pin<&mut {async fn body of wait()}>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::ptr::DynMetadata<dyn std::any::Any>`: 8 bytes, alignment: 8 bytes
print-type-size field `._vtable_ptr`: 8 bytes
print-type-size field `._phantom`: 0 bytes
print-type-size type: `std::ptr::NonNull<std::ptr::metadata::VTable>`: 8 bytes, alignment: 8 bytes
print-type-size field `.pointer`: 8 bytes
print-type-size type: `std::mem::ManuallyDrop<{async fn body of wait()}>`: 1 bytes, alignment: 1 bytes
print-type-size field `.value`: 1 bytes
print-type-size type: `std::mem::MaybeUninit<{async fn body of wait()}>`: 1 bytes, alignment: 1 bytes
@@ -32,3 +61,7 @@ print-type-size discriminant: 1 bytes
print-type-size variant `Unresumed`: 0 bytes
print-type-size variant `Returned`: 0 bytes
print-type-size variant `Panicked`: 0 bytes
print-type-size type: `std::marker::PhantomData<&str>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<*mut ()>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<dyn std::any::Any>`: 0 bytes, alignment: 1 bytes
print-type-size type: `std::marker::PhantomData<fn(&()) -> &()>`: 0 bytes, alignment: 1 bytes