可变大小堆栈帧的组装:这些堆栈对齐指令在分配 VLA 时似乎没用?

Assembly of variable-size stack frame: these stack-alignment instructions seem useless in allocating a VLA?

我正在阅读计算机系统:程序员的视角第 3 版,3.10.5 Supporting Variable-Size Stack Frames, Figure 3.43 中的程序集让我感到困惑。

本书的部分试图解释可变大小堆栈帧是如何生成的,并给出了一个C代码及其汇编版本作为示例。

这里是C和汇编的代码(书上图3.43):

我不知道第8-10行在汇编中有什么用。为什么不在第 7 行之后使用 movq %rsp, %r8

(a) C代码

long vframe(long n, long idx, long *q) {
    long i;
    long *p[n];
    p[0] = &i;
    for (i = 1; i < n; i++)
        p[i] = q;
    return *p[idx];
}

(b) 部分生成的汇编代码

vframe:
2:    pushq    %rbp
3:    movq     %rsp, %rbp  
4:    subq     , %rsp
5:    leaq     22(, %rdi, 8), %rax      
6:    andq     $-16, %rax       
7:    subq     %rax, %rsp
8:    leaq     7(%rsp), %rax
9:    shrq     , %rax
10:   leaq     0(, %rax, 8), %r8
11:   movq     %r8, %rcx
................................
12: L3:
13:   movq     %rdx, (%rcx, %rax, 8)
14:   addq     , %rax
15:   movq     %rax, -8(%rbp)
16: L2:
17:   movq     -8(%rbp), %rax
18:   cmpq     %rdi, %rax
19:   jl       L3
20:   leave
21:   ret

这是我的想法:

在第 7 行之后,%rsp 应该是 16 的倍数(%rsp 在调用 vframe 之前应该是 16 的倍数,因为堆栈帧对齐。当 vframe被调用,%rsp减8保存调用者的return地址,然后第2行的pushq指令又将%rsp减8,在第 4 行中是 16。因此在第 7 行的开头,%rsp 是 16 的倍数。在第 7 行中,%rsp 减去 %rax。由于第 6 行使得 %rax16的倍数,第7行的结果是设置%rsp16的倍数)表示%rsp的低4位全为0 然后第8行%rsp+7存入%rax,第9行%rax逻辑右移3位,第10行%rax*8存入%r8.

第7行之后,%rsp的低4位全为0。第 8 行 %rsp+7 只是让低 3 位全为 1,第 9 行截断这 3 位,第 10 行 %rax*8 让结果左移 3 位。所以最后的结果应该就是原来的%rsp(第7行的结果).

所以我想知道第8-10行是不是没用

为什么不在第 7 行之后使用 movq %rsp, %r8 并删除原来的第 8-10 行?

我认为一个有用的探索程序是将生成的代码减少为:

.globl _vframe
_vframe:
    pushq    %rbp
    movq     %rsp, %rbp  
    subq     , %rsp
    leaq     22(, %rdi, 8), %rax      
    andq     $-16, %rax       
    subq     %rax, %rsp
    leaq     7(%rsp), %rax
    shrq     , %rax
    leaq     0(, %rax, 8), %r8
    mov %r8, %rax
    sub %rsp, %rax
    leave
    ret

请注意,我只是删除了有用的代码,并返回了 %r8 和 %rsp 之间的差异。 然后写了个驱动:

extern void *vframe(unsigned long n);


#include <stdio.h>

int main(void) {
    int i;
    for (i = 0; i < (1<<18); i++) {
        void *p = vframe(i);
        if (p) {
            printf("%d %p\n", i, p);
        }
    }
    return 0;
}

去看看。他们总是一样的。所以为什么?当遇到给定的构造(var len 数组)时,它可能是标准代码发射。编译器必须维护某些标准,例如可跟踪的调用框架和对齐方式,os 可能只是将此代码作为已知的解决方案发出。可变长度数组通常被认为是语言中的错误;向 C++ 致敬,为 C 添加半工作、半思考的机制;因此编译器实现者可能不会过多关注代表他们生成的代码。