标签: register-allocation

注册分配和溢出,简单方法?

我正在寻找一种方法来将局部变量分配给寄存器.我知道有几种严肃的方法(即维基百科上提到的方法),但我仍然坚持如何"溢出"完成.此外,相关文献相当令人生畏.我希望有一些更简单的东西可以满足我的优先事项:

  1. 正确性 - 一种算法,无论有多少局部变量,都会生成正确的代码.
  2. 简单 - 我无需阅读太多文献即可理解.
  3. 效率 - 它需要比现有方法更好,即:

将操作转换x = y # z为:

movl y, %eax
movl z, %ebx
op %ebx, %eax
movl %eax, x
Run Code Online (Sandbox Code Playgroud)

由于我的目标是英特尔386,因此一些相关的限制是:

  • 二进制操作有两个参数,其中一个是源和目标.一元操作只需一个参数.
  • 操作只能访问一个内存位置; 因此,二进制运算需要寄存器中至少有一个参数.
  • 最多有六个寄存器:%eax %ebx %ecx %edx %esi %edi.(%ebp也可作为最后手段.)
  • 有一些特殊情况,例如整数除法和返回寄存器,但我现在可以忽略它们.

编译器目前有三个步骤:

  • i386ification:所有操作都转换为表单a = a # b(或a = #a用于一元操作).
  • 活力分析:确定每个操作之前和之后的活变量集.
  • 寄存器分配:构建干扰图并着色.

然后编译器抛出它的蜡笔,不知道接下来该做什么.

public int mf(int cr, int ci) {
    int i = 0;
    int zr = 0;
    int zi = …
Run Code Online (Sandbox Code Playgroud)

compiler-theory graph-theory register-allocation i386

26
推荐指数
2
解决办法
7692
查看次数

海湾合作委员会:禁止使用某些登记册

这是一个奇怪的要求,但我觉得它有可能.我想要的是将一些编译指示或指令插入到我的代码区域(用C编写),以便GCC的寄存器分配器不会使用它们.

我知道我可以做这样的事情,这可能会为这个变量留下这个寄存器

register int var1 asm ("EBX") = 1984;
register int var2 asm ("r9") = 101;
Run Code Online (Sandbox Code Playgroud)

问题是我直接插入新指令(用于硬件模拟器),而GCC和GAS还没有识别出这些指令.我的新指令可以使用现有的通用寄存器,我想确保我保留了一些(即r12-> r15).

现在,我在一个模拟环境中工作,我想快速做我的实验.将来我会添加GAS并将内在函数添加到GCC中,但是现在我正在寻找快速修复.

谢谢!

c assembly gcc gnu-assembler register-allocation

22
推荐指数
4
解决办法
3866
查看次数

在变量定义之前转到 - 它的值会发生什么?

这是我想知道的一些问题.鉴于以下代码,我们可以确定其输出吗?

void f() {
  int i = 0; 
  z: if(i == 1) goto x; else goto u; 
  int a; 
  x: if(a == 10) goto y; 
  u: a = 10; i = 1; goto z; 
  y: std::cout << "finished: " << a; 
}
Run Code Online (Sandbox Code Playgroud)

这是否保证finished: 10根据C++标准输出?或者编译器可以占用a存储到的寄存器,何时goto到达之前的位置a

c++ goto local-variables register-allocation

17
推荐指数
3
解决办法
1611
查看次数

寄存器变量的地址

在C中,我们不能使用&来查找寄存器变量的地址,但在C++中我们也可以这样做.为什么它在C++中合法而在C中不合法?有人可以深入解释这个概念.

c c++ register-allocation keyword

15
推荐指数
3
解决办法
8759
查看次数

为什么编译器坚持在这里使用被调用者保存的寄存器?

考虑这个 C 代码:

void foo(void);

long bar(long x) {
    foo();
    return x;
}
Run Code Online (Sandbox Code Playgroud)

当我在 GCC 9.3 上使用-O3或编译它时-Os,我得到这个:

bar:
        push    r12
        mov     r12, rdi
        call    foo
        mov     rax, r12
        pop     r12
        ret
Run Code Online (Sandbox Code Playgroud)

除了选择rbx而不是r12作为被调用者保存的寄存器之外,clang 的输出是相同的。

但是,我希望/期望看到看起来更像这样的程序集:

bar:
        push    rdi
        call    foo
        pop     rax
        ret
Run Code Online (Sandbox Code Playgroud)

由于无论如何您都必须将某些内容推送到堆栈中,因此将您的值推送到那里似乎更短,更简单,并且可能更快,而不是将一些任意的被调用者保存的寄存器值推送到那里,然后将您的值存储在该寄存器中。call foo当你把东西放回去后,反之亦然。

我的组装错了吗?它在某种程度上比弄乱额外的寄存器效率低吗?如果这两个的答案都是“否”,那么为什么 GCC 或 clang 不这样做呢?

Godbolt 链接


编辑:这是一个不太简单的例子,即使变量被有意义地使用,它也会发生:

long foo(long);

long bar(long x) {
    return foo(x * x) - x;
}
Run Code Online (Sandbox Code Playgroud)

我明白了:

bar:
        push    rbx …
Run Code Online (Sandbox Code Playgroud)

c assembly gcc x86-64 register-allocation

15
推荐指数
1
解决办法
737
查看次数

在 GCC 内联汇编中影响内存操作数寻址模式的早期破坏者的不正确行为的具体示例?

以下摘自GCC 手册的 Extended Asm docs,关于使用asm关键字在 C 中嵌入汇编指令:

如果一个输出参数 ( a ) 允许寄存器约束而另一个输出参数 ( b ) 允许内存约束,则会出现同样的问题。GCC 生成的用于访问b 中的内存地址的代码 可以包含可能由a共享的寄存器,并且 GCC 将这些寄存器视为 asm 的输入。如上所述,GCC 假设在写入任何输出之前消耗此类输入寄存器。如果 asm 语句在使用 b 之前写入 a,则此假设可能会导致不正确的行为。与对寄存器约束结合“&”改性剂确保修改一个不影响通过引用的地址b. 否则,位置b,如果未定义 一个使用之前改性b

斜体句子表示如果 asm 语句a在使用b.

我无法弄清楚这种“不正确的行为”是如何发生的,所以我希望有一个具体的 asm 代码示例来演示“不正确的行为”,以便我可以深入理解这一段。

当两个这样的 asm 代码并行运行时,我可以察觉到问题,但上面的段落没有提到多处理场景。

如果我们只有一个单核的CPU,能否请您出示一个asm代码,可能会产生这样的错误行为,即修改a影响引用的地址,b使得其位置b未定义。

我唯一熟悉的汇编语言是 Intel x86 汇编,因此请让示例针对该平台。

x86 assembly gcc register-allocation inline-assembly

11
推荐指数
2
解决办法
256
查看次数

如何强制gcc使用所有SSE(或AVX)寄存器?

我正在尝试为Windows x64目标编写一些计算密集型代码,使用SSE或新的AVX指令,在GCC 4.5.2和4.6.1,MinGW64(TDM GCC构建和一些自定义构建)中进行编译.我的编译器选项是-O3 -mavx.(-m64暗示)

简而言之,我想对4个打包浮点数的3D矢量进行一些冗长的计算.这需要4x3 = 12 xmm或ymm寄存器用于存储,2或3个寄存器用于临时结果.这应该恕我直言,适合64位目标可用的16个SSE(或AVX)寄存器.但是,GCC使用寄存器溢出产生非常不理想的代码,仅使用寄存器xmm0-xmm10并将数据从堆栈中移入和移入堆栈.我的问题是:

有没有办法说服GCC使用所有寄存器xmm0-xmm15

要修改想法,请考虑以下SSE代码(仅供参考):

void example(vect<__m128> q1, vect<__m128> q2, vect<__m128>& a1, vect<__m128>& a2) {
    for (int i=0; i < 10; i++) {
        vect<__m128> v = q2 - q1;
        a1 += v;
//      a2 -= v;

        q2 *= _mm_set1_ps(2.);
    }
}
Run Code Online (Sandbox Code Playgroud)

这里vect<__m128>只是一个struct 3 __m128,有自然的加法和乘以标量.当行a2 -= v被注释掉时,即我们只需要3x3寄存器进行存储,因为我们忽略了a2,所产生的代码确实很简单,没有移动,所有内容都在寄存器中执行xmm0-xmm10.当我删除注释时a2 -= v,代码非常糟糕,寄存器和堆栈之间有很多混乱.即使编译器可以只使用寄存器xmm11-xmm13或其他东西.

我实际上还没有看到GCC xmm11-xmm15在我的所有代码中的任何地方使用任何寄存器.我究竟做错了什么?我知道它们是被调用者保存的寄存器,但这种开销完全可以通过简化循环代码来证明.

64-bit gcc sse register-allocation avx

9
推荐指数
2
解决办法
6479
查看次数

x86寄存器重命名的成本

以下代码使用amd64上的gcc或clang编译:

// gcc -O2 file.c -c
int f(int a, int b, int c, int d)
{
    return a & b & c & d;
}
Run Code Online (Sandbox Code Playgroud)

产生以下装配:

0000000000000000 <f>:
  0:    89 d0                   mov    %edx,%eax
  2:    21 c8                   and    %ecx,%eax
  4:    21 f0                   and    %esi,%eax
  6:    21 f8                   and    %edi,%eax
  8:    c3                      retq  
Run Code Online (Sandbox Code Playgroud)

由于按位and应该是关联的,人们会认为将成对方式累加到两个寄存器然后再and这两个寄存器会更有效.这将破坏依赖性并允许在具有多个ALU的cpu上并行执行.

由于编译器and为所有操作进入相同的寄存器,我假设它依赖于cpu能够进行寄存器重命名以打破依赖本身.

cpu的寄存器重命名功能是否没有成本,并且始终在amd64上可用,或者为什么编译器会像这样编译代码?

更新: 我发现如果为树关联宽度传递更高的值,gcc可以执行预期的依赖关系链断开:

--param tree-reassoc-width=2
Run Code Online (Sandbox Code Playgroud)

c assembly x86-64 renaming register-allocation

7
推荐指数
1
解决办法
881
查看次数

基于寄存器+堆栈的虚拟机如何工作?

我知道如何基于寄存器以及基于堆栈的虚拟机如何独立工作.我知道两者的优点和缺点.我想知道的是,有没有人试图合并这两者?

我试图在网上搜索这种虚拟机的存在,但无济于事.我得到的最好结果是一篇关于混合虚拟机(HyVM)的文章.如果确实为编程语言创建了这样的虚拟机,我将有兴趣查看其源代码以了解它是如何工作的.

也许有人可以指出我找到这样一个虚拟机的正确方向,或者将我链接到本主题中详述的文章或博客文章.

register-allocation stack-based cpu-registers ssa vm-implementation

6
推荐指数
1
解决办法
586
查看次数

在GCC中使用内联ARM汇编时优化使用的寄存器

我想在我的C代码中编写一些内联ARM程序集.对于这段代码,我需要使用一个或两个寄存器,而不仅仅是声明为函数输入和输出的寄存器.我知道如何使用clobber列表告诉GCC我将使用一些额外的寄存器来进行计算.

但是,我确信GCC可以自由地调整哪些寄存器用于优化时的内容.也就是说,我觉得使用固定寄存器进行计算是个坏主意.

在不使用固定寄存器的情况下,使用一些既不是输入也不输出内联汇编的额外寄存器的最佳方法是什么?

PS我以为使用虚拟输出变量可能会成功,但我不确定会有什么样的奇怪的其他效果...

gcc arm register-allocation inline-assembly cpu-registers

5
推荐指数
1
解决办法
1433
查看次数