编译器是否通常将寄存器用于其“预期”目的?

Jam*_*ton 4 compiler-construction x86 assembly compilation x86-64

我一直在学习汇编,并且我读到四个主要的 x86 通用寄存器(eax、ebx、ecx 和 edx)每个都有一个预期或建议的用途。例如,eax 是累加器寄存器,ecx 用作循环的计数器,等等。大多数编译器是否尝试将寄存器用于建议的目的,或者他们是否忽略了寄存器“应该”的用途而只是将值分配给下一个可用的寄存器?

此外,在查看 x64 寄存器时,我注意到添加了额外的 8 个通用寄存器,如果忽略 rbp、rsp、rsi 和 rdi(因为它们具有非通用用途),gp 寄存器的总数将达到 12 ),如果包括它们,则为十六个。在普通用户程序(即浏览器、文字处理器等,而不是需要大量寄存器的密码程序)中,在任何给定时间通常使用多少这些寄存器?像 Firefox 这样的程序一次使用所有 12/16 个普通寄存器是常见的,还是因为它们没有足够的变量来填充它们而只使用一个子集?我将通过反汇编二进制文件来自己研究这个问题,看看一般情况是什么,但我希望能得到比我更有见识的人的回答。

此外,如果当前未用于非通用应用程序,编译器是否通常将 semi-gp 寄存器(rsi、rdi、rsp 和 rbp)用于通用用途?我很好奇,因为我看到这些寄存器被列为“通用”,但即使我能想到这些寄存器不能用于通用存储的实例(例如,你不想存储rbp 和 rsp 变量,然后将值推入堆栈!)。那么编译器会尽量使用这些寄存器吗?x86 和 x64 编译之间是否有区别,因为 x64 处理器有更多可用寄存器,因此没有必要将变量填充到任何可用寄存器中?

Mar*_*oom 6

所有 GP 寄存器都是通用的。
只有在执行特定的(通常是遗留的)指令时,它们才具有特殊含义。

例如,四联rsirdirbprsp只有后者有一个特殊的目的,这是由于指令等callretpush等。
如果您不使用它们,即使是隐式使用(不可否认,这是一种不太可能的情况),您可以将其用作累加器。

这个原则是通用的,编译器会利用它。

考虑这个人为的例子[ 1 ]

void maxArray(int* x, int* y, int*z, short* w) {
    for (int i = 0; i < 65536; i++)
    {
        int a = y[i]*z[i];
        int b = z[i]*z[i];
        int c = y[i]*x[i]-w[i];
        int d = w[i]+x[i]-y[i];
        int e = y[i+1]*w[i+2];
        int f = w[i]*w[i];

        x[i] = a*a-b+d; 
        y[i] = b-c*d/f+e;
        z[i] = (e+f)*2-4*a*d;
        w[i] = a*b-c*d+e*f;
    }
}
Run Code Online (Sandbox Code Playgroud)

它由 GCC 编译到此列表中

maxArray(int*, int*, int*, short*):
        push    r13
        push    r12
        xor     r8d, r8d
        push    rbp
        push    rbx
        mov     r12, rdx
.L2:
        mov     edx, DWORD PTR [rsi+r8*2]    
        mov     ebp, DWORD PTR [r12+r8*2]
        movsx   r11d, WORD PTR [rcx+r8]
        mov     eax, DWORD PTR [rdi+r8*2]
        movsx   ebx, WORD PTR [rcx+4+r8]
        mov     r9d, edx
        mov     r13d, edx
        imul    r9d, ebp
        imul    r13d, eax
        lea     r10d, [rax+r11]
        imul    ebx, DWORD PTR [rsi+4+r8*2]
        mov     eax, r9d
        sub     r10d, edx
        imul    ebp, ebp
        sub     r13d, r11d
        imul    eax, r9d
        imul    r11d, r11d
        sub     eax, ebp
        add     eax, r10d
        mov     DWORD PTR [rdi+r8*2], eax
        mov     eax, r13d
        imul    eax, r10d
        cdq
        idiv    r11d
        mov     edx, ebp
        sub     edx, eax
        mov     eax, edx
        lea     edx, [0+r9*4]
        add     eax, ebx
        mov     DWORD PTR [rsi+r8*2], eax
        lea     eax, [rbx+r11]
        imul    r9d, ebp
        imul    r11d, ebx
        add     eax, eax
        imul    edx, r10d
        add     r9d, r11d
        imul    r10d, r13d
        sub     eax, edx
        sub     r9d, r10d
        mov     DWORD PTR [r12+r8*2], eax
        mov     WORD PTR [rcx+r8], r9w
        add     r8, 2
        cmp     r8, 131072
        jne     .L2
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        ret
Run Code Online (Sandbox Code Playgroud)

可以看到大部分的GP寄存器都用到了(我没统计过),包括rbp,rsirdi
寄存器的使用不限于它们的规范形式。

注意在这个例子中rsirdi用于加载和读取(对于每个寄存器)一个数组,这是一个巧合。
这些寄存器用于传递前两个整数/指针参数。

maxArray(int*, int*, int*, short*):
        push    r13
        push    r12
        xor     r8d, r8d
        push    rbp
        push    rbx
        mov     r12, rdx
.L2:
        mov     edx, DWORD PTR [rsi+r8*2]    
        mov     ebp, DWORD PTR [r12+r8*2]
        movsx   r11d, WORD PTR [rcx+r8]
        mov     eax, DWORD PTR [rdi+r8*2]
        movsx   ebx, WORD PTR [rcx+4+r8]
        mov     r9d, edx
        mov     r13d, edx
        imul    r9d, ebp
        imul    r13d, eax
        lea     r10d, [rax+r11]
        imul    ebx, DWORD PTR [rsi+4+r8*2]
        mov     eax, r9d
        sub     r10d, edx
        imul    ebp, ebp
        sub     r13d, r11d
        imul    eax, r9d
        imul    r11d, r11d
        sub     eax, ebp
        add     eax, r10d
        mov     DWORD PTR [rdi+r8*2], eax
        mov     eax, r13d
        imul    eax, r10d
        cdq
        idiv    r11d
        mov     edx, ebp
        sub     edx, eax
        mov     eax, edx
        lea     edx, [0+r9*4]
        add     eax, ebx
        mov     DWORD PTR [rsi+r8*2], eax
        lea     eax, [rbx+r11]
        imul    r9d, ebp
        imul    r11d, ebx
        add     eax, eax
        imul    edx, r10d
        add     r9d, r11d
        imul    r10d, r13d
        sub     eax, edx
        sub     r9d, r10d
        mov     DWORD PTR [r12+r8*2], eax
        mov     WORD PTR [rcx+r8], r9w
        add     r8, 2
        cmp     r8, 131072
        jne     .L2
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        ret
Run Code Online (Sandbox Code Playgroud)

  • @JamesPreston 非常常见,大多数整数变量是 32 位,但指针是 64 位。在 x86 上用完寄存器很常见,在 x64 上没有那么多,所以典型的寄存器压力显然大于 6 但小于 14 (5认同)