Dlang - 在汇编中理解 std.cycle()
Dlang - Understaning std.cycle() in assembly
import std.range : cycle;
void foo() pure @safe {
cycle([1, 2]);
}
今天遇到一个D语言写的程序。我试图从一个简单的函数开始理解它的汇编代码。
来自the asm output on the D compiler explorer:
pure nothrow @nogc @safe std.range.Cycle!(int[]).Cycle std.range.cycle!(int[]).cycle(int[]):
push rbp
mov rbp,rsp
sub rsp,0x40
mov QWORD PTR [rbp-0x20],rdi
mov QWORD PTR [rbp-0x10],rsi
mov QWORD PTR [rbp-0x8],rdx
... rest of the function
我试过好几次了,但不明白为什么
std.range.cycle()
得到 3 个参数(RDI
、RSI
和 RDX
),或者我的范围是 ([1, 2]
)。不是类C的结构吗?
还是我遗漏了什么?
看起来您正在使用基于 rdi 和 rsi 的 x86-64 SystemV ABI 进行 arg 传递,因为 Windows 64 位 ABI 使用不同的 regs。参见 x86 tag wiki for links to ABI docs, or see the current revision here.
按值传递的小对象(如结构)进入多个整数寄存器。按值返回大对象(超过 128 位)也使用调用者分配的指向 space 的隐藏指针,而不是打包到 RDX:RAX。这就是您的函数中发生的事情。
基于asm和docs,我认为一个Cycle对象具有三个值:start、end和index。我根本不知道 D,但这很有意义。由于它们都是 64 位的,这使得它太大而无法放入 RDX:RAX,因此它由隐藏指针 return 编辑。
进入 Cycle() 的参数传递寄存器是:
- RDI:"hidden" 指向 return 值的指针(它是三个 64 位整数的结构)
- RSI:范围 arg 的第一个成员(我称之为 range_start)
- RDX:Range arg 的第二个成员(我称之为 range_end)
我启用了优化以获得更具可读性的 asm 而没有那么多噪音,但不幸的是,看起来这个 D 编译器不如 clang 或 gcc 复杂。使用 -O -release -inline
(as recommended by this page),它仍然对堆栈执行 lot of store/reload。
pure nothrow @nogc @safe std.range.Cycle!(int[]).Cycle std.range.cycle!(int[]).cycle(int[]):
sub rsp,0x28
mov QWORD PTR [rsp+0x20],rdi # hidden first arg (return-value pointer).
mov QWORD PTR [rsp+0x8],0x0 # totally useless: overwritten without read
mov QWORD PTR [rsp+0x10],0x0 # totally useless: same.
mov QWORD PTR [rsp+0x8],rsi # first "real" arg
mov QWORD PTR [rsp+0x10],rdx # second "real" arg
xor eax,eax
xor edx,edx # zero rax:rdx. Perhaps from the index=0 default when you only use one arg?
div QWORD PTR [rsp+0x8] # divide 0 by first arg of the range.
mov QWORD PTR [rsp+0x18],rdx # remainder of (index / range_start), I guess.
lea rsi,[rsp+0x8] # RSI=pointer to where range_start, range_end, and index/range_start were stored on the stack.
movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi] # copy to the dst buffer. A smart compiler would have stored there in the first place, instead of to local scratch and then copying.
movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi] # movs is not very efficient, this is horrible code.
movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
mov rax,QWORD PTR [rsp+0x20] # mov rax, rdi before those MOVS instructions would have been much more efficient.
add rsp,0x28
ret
ABI 需要 return 大对象到 return RAX 中隐藏指针的函数,因此调用者不必单独保留指向 return 缓冲。这就是该函数完全设置 RAX 的原因。
一个好的编译器会这样做:
std.range.Cycle...:
mov [rdi], rsi # cycle_start
mov [rdi+0x8], rdx # cycle_end
mov [rdi+0x10], 0 # index
mov rax, rdi
ret
或者完全内联对 Cycle 的调用,因为它很简单。实际上,我认为它 确实 内联到 foo() 中,但是仍然发出了 cycle() 的独立定义。
我们无法判断 foo()
调用了哪两个函数,因为编译器资源管理器似乎在不解析符号的情况下反汇编 .o(不是链接的二进制文件)。所以调用偏移量是00 00 00 00
,链接器的占位符。但它可能正在调用内存分配函数,因为它使用 esi=2 和 edi=0 进行调用。 (). The call target shows as the next instruction, because that's where call's rel32 displacement 数自.
希望 LDC or GDC do a better job, since they're based on modern optimizing backends (LLVM and gcc), but the compiler-explorer site you linked doesn't have those compilers installed. If there's another site based on Matt Godbolt's compiler explorer code,但对于其他 D 编译器,那会很酷。
import std.range : cycle;
void foo() pure @safe {
cycle([1, 2]);
}
今天遇到一个D语言写的程序。我试图从一个简单的函数开始理解它的汇编代码。
来自the asm output on the D compiler explorer:
pure nothrow @nogc @safe std.range.Cycle!(int[]).Cycle std.range.cycle!(int[]).cycle(int[]):
push rbp
mov rbp,rsp
sub rsp,0x40
mov QWORD PTR [rbp-0x20],rdi
mov QWORD PTR [rbp-0x10],rsi
mov QWORD PTR [rbp-0x8],rdx
... rest of the function
我试过好几次了,但不明白为什么
std.range.cycle()
得到 3 个参数(RDI
、RSI
和 RDX
),或者我的范围是 ([1, 2]
)。不是类C的结构吗?
还是我遗漏了什么?
看起来您正在使用基于 rdi 和 rsi 的 x86-64 SystemV ABI 进行 arg 传递,因为 Windows 64 位 ABI 使用不同的 regs。参见 x86 tag wiki for links to ABI docs, or see the current revision here.
按值传递的小对象(如结构)进入多个整数寄存器。按值返回大对象(超过 128 位)也使用调用者分配的指向 space 的隐藏指针,而不是打包到 RDX:RAX。这就是您的函数中发生的事情。
基于asm和docs,我认为一个Cycle对象具有三个值:start、end和index。我根本不知道 D,但这很有意义。由于它们都是 64 位的,这使得它太大而无法放入 RDX:RAX,因此它由隐藏指针 return 编辑。
进入 Cycle() 的参数传递寄存器是:
- RDI:"hidden" 指向 return 值的指针(它是三个 64 位整数的结构)
- RSI:范围 arg 的第一个成员(我称之为 range_start)
- RDX:Range arg 的第二个成员(我称之为 range_end)
我启用了优化以获得更具可读性的 asm 而没有那么多噪音,但不幸的是,看起来这个 D 编译器不如 clang 或 gcc 复杂。使用 -O -release -inline
(as recommended by this page),它仍然对堆栈执行 lot of store/reload。
pure nothrow @nogc @safe std.range.Cycle!(int[]).Cycle std.range.cycle!(int[]).cycle(int[]):
sub rsp,0x28
mov QWORD PTR [rsp+0x20],rdi # hidden first arg (return-value pointer).
mov QWORD PTR [rsp+0x8],0x0 # totally useless: overwritten without read
mov QWORD PTR [rsp+0x10],0x0 # totally useless: same.
mov QWORD PTR [rsp+0x8],rsi # first "real" arg
mov QWORD PTR [rsp+0x10],rdx # second "real" arg
xor eax,eax
xor edx,edx # zero rax:rdx. Perhaps from the index=0 default when you only use one arg?
div QWORD PTR [rsp+0x8] # divide 0 by first arg of the range.
mov QWORD PTR [rsp+0x18],rdx # remainder of (index / range_start), I guess.
lea rsi,[rsp+0x8] # RSI=pointer to where range_start, range_end, and index/range_start were stored on the stack.
movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi] # copy to the dst buffer. A smart compiler would have stored there in the first place, instead of to local scratch and then copying.
movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi] # movs is not very efficient, this is horrible code.
movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
mov rax,QWORD PTR [rsp+0x20] # mov rax, rdi before those MOVS instructions would have been much more efficient.
add rsp,0x28
ret
ABI 需要 return 大对象到 return RAX 中隐藏指针的函数,因此调用者不必单独保留指向 return 缓冲。这就是该函数完全设置 RAX 的原因。
一个好的编译器会这样做:
std.range.Cycle...:
mov [rdi], rsi # cycle_start
mov [rdi+0x8], rdx # cycle_end
mov [rdi+0x10], 0 # index
mov rax, rdi
ret
或者完全内联对 Cycle 的调用,因为它很简单。实际上,我认为它 确实 内联到 foo() 中,但是仍然发出了 cycle() 的独立定义。
我们无法判断 foo()
调用了哪两个函数,因为编译器资源管理器似乎在不解析符号的情况下反汇编 .o(不是链接的二进制文件)。所以调用偏移量是00 00 00 00
,链接器的占位符。但它可能正在调用内存分配函数,因为它使用 esi=2 和 edi=0 进行调用。 (
希望 LDC or GDC do a better job, since they're based on modern optimizing backends (LLVM and gcc), but the compiler-explorer site you linked doesn't have those compilers installed. If there's another site based on Matt Godbolt's compiler explorer code,但对于其他 D 编译器,那会很酷。