为什么Rust库中的异步函数不生成poll函数?

huangapple go评论95阅读模式
英文:

Why does the async function in rust lib not generate a poll function

问题

当我在main.rs中定义一个异步函数时,异步函数会生成一个poll函数和一个上下文初始化函数,就像这样:

pub async fn hello_world() {
    println!("hello world!!!");
}

#[tokio::main]
async fn main() {
    hello_world().await;
}

从符号表中可以看出,async hello_world函数生成了两个函数:Future trait中的Poll函数和上下文初始化函数。使用命令readelf -sW hello_world可以查看符号表中的名称为_ZN15test_helloworld11hello_world28_$u7b$$u7b$closure$u7d$$u7d$17hfb37a71ab872a722E_ZN15test_helloworld11hello_world17h68b666166140c723E

但是当我在lib.rs中定义异步函数时,没有生成poll函数,就像这样:

pub async fn hello_world() {
    println!("hello world!!!");
}

pub async fn start_world() {
    hello_world().await;
}

使用命令cargo rustc -- --emit=obj将Rust库编译为obj后,从符号表中可以看出hello_world函数只有一个上下文初始化函数,没有Future的poll函数。使用命令readelf -sW xxx.o可以查看符号表中的名称,只有_ZN14helloworld_lib11hello_world17h3e453d3cd706914eE,缺少obj中的poll函数。

这让我很困惑。在Rust中,异步是一种语法糖,解糖后会生成一个poll函数,但为什么编译器没有生成它呢?这个poll函数是在什么时候生成的?我可以添加编译选项让Rust编译器生成这个poll函数吗?

英文:

When I define an async function in main.rs, the async function generates a poll function and a context initialization function, like this:

pub async fn hello_world()
{
    println!("hello world!!!");
}

#[tokio::main]
async fn main() {
    hello_world().await;
}

From the symbol table, it can be seen that async hello_world function generates two functions: Poll function in Future trait and context initialization function. Use command readelf -sW hello_world to see the names in the symbol table are _ZN15test_helloworld11hello_world28_$u7b$$u7b$closure$u7d$$u7d$17hfb37a71ab872a722E
and
_ZN15test_helloworld11hello_world17h68b666166140c723E.

But when I defined asyn functions in lib.rs, there was no poll function,like this:

pub async fn hello_world()
{
    println!("hello world!!!");
}

pub async fn start_world()
{
    hello_world().await;
}

After compiling rust lib into an obj using the command cargo rustc -- --emit=obj, it can be seen from the symbol table that the hello_worldfunction only has a context initialization function and no Future poll function. Use command readelf -sW xxx.o to see the names in the symbol table, only has _ZN14helloworld_lib11hello_world17h3e453d3cd706914eE, Missing poll function in obj.

This makes me very confused. In rust, async is a syntax sugar that will generate a poll function after disaggregating the sugar, but why did the compiler not generate it? When was this poll function generated? Can I add compilation options to allow the rust compiler to generate this poll function?

答案1

得分: 1

异步函数返回实现了Future trait的类型。直到需要使用该trait实现的某个地方,机器码才会被生成;在这种情况下,编译器会将实现代码作为 Rust MIR(中级中间表示)提供给编译使用的地方,而不是生成机器码。(我不知道编译器为什么会做出这个选择;可能是因为泛型代码很常见作为内联的候选项,所以生成很少被调用的机器码函数是不值得的。)

我们可以通过编写一个明确返回dyn Future的普通函数来强制包含一个实现。当 Rust 代码将具体类型(例如start_world()的返回类型)强制转换为dyn类型(例如dyn Future)时,会生成dyn分发的虚函数表(vtable),因此编写以下函数将强制生成一个虚函数表及其中的函数,因为返回Box会执行这样的强制转换:

pub fn concrete_start() -> Pin<Box<dyn Future<Output = ()>>> {
    Box::pin(start_world())
}

(对于这个示例来说,固定引用(pinning)并不是必需的,但在返回这种类型的任何实际函数中都会存在。它不会改变机器码。)

现在让我们使用cargo-show-asm查看生成的代码。你不需要对处理器的汇编语言(在这个示例中是x86)了解得很多,只需要查看是否存在感兴趣的标签/符号即可知道_存在什么_,因为你可以查找有趣的标签/符号的存在。concrete_start()出现了:

scratchpad::concrete_start:
Lfunc_begin4:
	push rbp
	mov rbp, rsp
	mov rax, qword ptr [rip + ___rust_no_alloc_shim_is_unstable@GOTPCREL]
	movzx eax, byte ptr [rax]
	mov edi, 2
	mov esi, 1
	call ___rust_alloc
	test rax, rax
	je LBB4_2
	mov word ptr [rax], 0
	lea rdx, [rip + l___unnamed_1]
	pop rbp
	ret
LBB4_2:
	mov edi, 1
	mov esi, 2
	call alloc::alloc::handle_alloc_error

当然,其中大部分是Box的分配。但l___unnamed_1Future实现的虚函数表:

l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
2
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
1
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0
l___unnamed_1:
	.quad	core::ptr::drop_in_place<scratchpad::start_world::{{closure}}>
	.asciz	"\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	scratchpad::start_world::{{closure}}
0"
.quad scratchpad::start_world::{{closure}}

该虚函数表包含指向类型的_drop glue_(一个知道如何释放/销毁/释放内存的函数)、其大小和对齐方式的指针,以及指向Future::poll()实现的指针:

scratchpad::start_world::{{closure}}:
Lfunc_begin3:
	push rbp
	mov rbp, rsp
	push rbx
	sub rsp, 56
	mov rbx, rdi
	movzx eax, byte ptr [rdi]
	lea rcx, [rip + LJTI3_0]
	movsxd rax, dword ptr [rcx + 4*rax]
	add rax, rcx
	jmp rax

	mov byte ptr [rbx + 1], 0
	jmp LBB3_7

	movzx eax, byte ptr [rbx + 1]
	test eax, eax
	jne LBB3_4
LBB3_7:
	lea rax, [rip + l___unnamed_2]
	mov qword ptr [rbp - 56], rax
	mov qword ptr [rbp - 48], 1
	mov qword ptr [rbp - 24], 0
	lea rax, [rip + l___unnamed_3]
	mov qword ptr [rbp - 40], rax
	mov qword ptr [rbp - 32], 0
	lea rdi, [rbp - 56]
	call std::io::stdio::_print
	mov word ptr [rbx], 257
	xor eax, eax
	add rsp, 56
	pop rbx
	pop rbp
	ret
LBB3_4:
	cmp eax, 1
	jne LBB3_10
	mov esi, 35
	lea rdi, [rip + _str.0]
	jmp LBB3_11

	lea rdi, [rip + _str.1]
	lea rdx, [rip + l___unnamed_4]
	mov esi, 34
	call core::panicking::panic

	lea rdi, [rip + _str.0]
	lea rdx, [rip + l___unnamed_4]
	mov esi, 35
	call core::panicking::panic
LBB3_10:
	mov esi, 34
	lea rdi, [rip + _str.1]
LBB3_11:
	lea rdx, [rip + l___unnamed_5]
	call core::panicking::panic
	ud2

	mov byte ptr [rbx + 1], 2
	jmp LBB3_14

LBB3_14:
	mov byte ptr [rbx], 2
	mov rdi, rax
	call __Unwind_Resume

这里还有你的println!()调用,以及一堆检查无效状态转换的代码,比如在Future完成后继续对其进行轮询。以下是这些 panic 的消息字符串:

.section __TEXT,__const
	.p2align	4, 0x0
_str.0:
	.ascii	"`async fn` resumed after completion"

	.p2align	4, 0x0
_str.1:
	.ascii	"`async fn` resumed after panicking"
英文:

Async functions return types that implement the Future trait. Machine code for trait implementations may not be generated until it is needed, by some use of the trait implementation; in this case, the compiler compiling the use-site gets the implementation code as Rust MIR (mid-level intermediate representation), not machine code. (I don't know why the compiler makes this choice; it may be because generic code is very often a candidate for inlining, so emitting machine code functions that would be rarely called is not worthwhile.)

We can force an implementation to be included by writing a plain function that explicitly returns a dyn Future. Vtables for dyn dispatch are generated when, and only when, the Rust code coerces a concrete type (such as the return type of start_world()) to a dyn type (such as dyn Future), so writing the following function will force a vtable, and the functions in it, to be generated, because returning the Box performs such a coercion:

pub fn concrete_start() -&gt; Pin&lt;Box&lt;dyn Future&lt;Output = ()&gt;&gt;&gt; {
    Box::pin(start_world())
}

(Pinning isn't necessary for this demonstration, but would be present in any practical function returning this type. It doesn't change the machine code.)

Now let's look at the generated code with cargo-show-asm. You don't need to know very much about the assembly language for your processor (in this example, x86) to be able to tell what is present, because you can just look for the presence of interesting labels/symbols. concrete_start() appears:

scratchpad::concrete_start:
Lfunc_begin4:
	push rbp
	mov rbp, rsp
	mov rax, qword ptr [rip + ___rust_no_alloc_shim_is_unstable@GOTPCREL]
	movzx eax, byte ptr [rax]
	mov edi, 2
	mov esi, 1
	call ___rust_alloc
	test rax, rax
	je LBB4_2
	mov word ptr [rax], 0
	lea rdx, [rip + l___unnamed_1]
	pop rbp
	ret
LBB4_2:
	mov edi, 1
	mov esi, 2
	call alloc::alloc::handle_alloc_error

Of course, most of this is the Box allocation. But l___unnamed_1 is in the vtable for the Future implementation:

l___unnamed_1:
	.quad	core::ptr::drop_in_place&lt;scratchpad::start_world::{{closure}}&gt;
	.asciz	&quot;\002\000\000\000\000\000\000\000\001\000\000\000\000\000\000&quot;
	.quad	scratchpad::start_world::{{closure}}

This vtable consists of a pointer to the type's drop glue (a function that knows to drop/destruct/deallocate it), its size and alignment, and a pointer to the Future::poll() implementation:

scratchpad::start_world::{{closure}}:
Lfunc_begin3:
	push rbp
	mov rbp, rsp
	push rbx
	sub rsp, 56
	mov rbx, rdi
	movzx eax, byte ptr [rdi]
	lea rcx, [rip + LJTI3_0]
	movsxd rax, dword ptr [rcx + 4*rax]
	add rax, rcx
	jmp rax

	mov byte ptr [rbx + 1], 0
	jmp LBB3_7

	movzx eax, byte ptr [rbx + 1]
	test eax, eax
	jne LBB3_4
LBB3_7:
	lea rax, [rip + l___unnamed_2]
	mov qword ptr [rbp - 56], rax
	mov qword ptr [rbp - 48], 1
	mov qword ptr [rbp - 24], 0
	lea rax, [rip + l___unnamed_3]
	mov qword ptr [rbp - 40], rax
	mov qword ptr [rbp - 32], 0
	lea rdi, [rbp - 56]
	call std::io::stdio::_print
	mov word ptr [rbx], 257
	xor eax, eax
	add rsp, 56
	pop rbx
	pop rbp
	ret
LBB3_4:
	cmp eax, 1
	jne LBB3_10
	mov esi, 35
	lea rdi, [rip + _str.0]
	jmp LBB3_11

	lea rdi, [rip + _str.1]
	lea rdx, [rip + l___unnamed_4]
	mov esi, 34
	call core::panicking::panic

	lea rdi, [rip + _str.0]
	lea rdx, [rip + l___unnamed_4]
	mov esi, 35
	call core::panicking::panic
LBB3_10:
	mov esi, 34
	lea rdi, [rip + _str.1]
LBB3_11:
	lea rdx, [rip + l___unnamed_5]
	call core::panicking::panic
	ud2

	mov byte ptr [rbx + 1], 2
	jmp LBB3_14

LBB3_14:
	mov byte ptr [rbx], 2
	mov rdi, rax
	call __Unwind_Resume

And there's your println!() call, as well as a bunch of checks for invalid state transitions, like the Future being polled after it completes. Here's the message strings for those panics:

.section __TEXT,__const
	.p2align	4, 0x0
_str.0:
	.ascii	&quot;`async fn` resumed after completion&quot;

	.p2align	4, 0x0
_str.1:
	.ascii	&quot;`async fn` resumed after panicking&quot;

huangapple
  • 本文由 发表于 2023年8月8日 23:19:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76860966.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定