簡體   English   中英

為什么gcc(ARM)不使用全局寄存器變量作為源操作數?

[英]Why gcc (ARM) aren't using Global Register Variables as source operands?

這是一個交流源代碼示例:

register int a asm("r8");
register int b asm("r9");

int main() {
    int c;
    a=2;
    b=3;
    c=a+b;
    return c;
}

這是使用arm gcc交叉編譯器生成的匯編代碼:

$ arm-linux-gnueabi-gcc  -c global_reg_var_test.c -Wa,-a,-ad

...
mov     r8, #2
mov     r9, #3
mov     r2, r8
mov     r3, r9
add     r3, r2, r3
...

使用-frename-registers時,行為是相同的。 (更新。在我對-O3說過之前。)

所以問題是:為什么gcc會添加第三和第四MOV而不是“ ADD R3,R8,R9”?

上下文:我需要在不重命名寄存器的模擬有序cpu(gem5 arm minorcpu)中優化代碼。

我以真實的例子(發表在評論中) 並將其放在godbolt編譯器資源管理器上 calc()的主要src1src1src2是全局變量,必須從內存中加載,而不是在寄存器中傳遞args。

我沒有看main ,只是看calc

register int sum asm ("r4");
register int r asm ("r5");
register int c asm ("r6");
register int k asm ("r7");
register int temp1 asm ("r8");    // really?  you're using two global register vars for scratch temporaries?  Just let the compiler do its job.
register int temp2 asm ("r9");
register long n asm ("r10");
int *src1, *src2, *dst;

void calc() {
  temp1 = r*n;
  temp2 = k*n;

  temp1 = temp1+k;
  temp2 = temp2+c;

  // you get bad code for this because src1 and src2 are globals, not args passed in regs
  sum = sum + src1[temp1] * src2[temp2];
}

    # gcc 4.8.2 -O3 -Wall -Wextra -Wa,-a,-ad -fverbose-asm
    mla     r0, r10, r7, r6          @ temp2.9, n, k, c   @@ tmp = k*n + c
    movw    r3, #:lower16:.LANCHOR0  @ tmp136,
    mla     r8, r10, r5, r7          @ temp1, n, r, k     @@ temp1 = r*n + k
    movt    r3, #:upper16:.LANCHOR0  @ tmp136,
    ldmia   r3, {r1, r2}             @ tmp136,,           @@ load both pointers, since they're stored adjacently in memory
    mov     r9, r0                   @ temp2, temp2.9     @@ This insn is wasted: the first MLA should have had this as the dest
    ldr     r3, [r1, r8, lsl #2]     @ *_22, *_22
    ldr     r2, [r2, r9, lsl #2]     @ *_28, *_28
    mla     r4, r2, r3, r4           @ sum, *_28, *_22, sum
    bx      lr                       @

出於某種原因,整數乘法累加( mla )指令之一使用r8temp1 )作為目標,但另一條指令寫入r0 (暫存寄存器),並且僅在以后將結果移至r9temp2 )。

sum += src1[temp1] * src2[temp2]是通過讀和寫r4mlasum )完成的。

為什么需要temp1temp2才能成為全局變量 這只會阻止優化器進行激進的優化,而這些優化不會計算出與C源代碼完全相同的臨時時間。 幸運的是,C內存模型足夠脆弱,以至於它應該能夠對它們進行重新排序,盡管這實際上可能就是為什么它沒有直接將MLA直接放入temp2 ,因為它決定首先進行該計算。 (嗯,內存模型甚至適用嗎?其他線程根本看不到我們的寄存器,因此這些全局變量實際上都是線程局部的。它應該允許對全局變量的分配放寬順序。信號處理程序可以看到這些全局變量,並且可以gcc並不遵循嚴格的源代碼順序,因為在源代碼中,兩個乘積都在兩個加法之前進行。)

Godbolt沒有較新的ARM gcc版本,因此我無法輕松地測試較新的gcc。 較新的gcc可能會做得更好。


順便說一句, 我嘗試使用局部變量作為臨時函數,但實際上並沒有得到更好的結果 可能是因為仍然有太多的寄存器全局變量,以至於gcc無法為臨時變量選擇方便的reg。

// same register globals, except for temp1 and temp2.

void calc_local_tmp() {
  int t1 = r*n + k;
  sum += src1[t1] * src2[k*n + c];
}
    push    {lr}                      @ gcc decides to push to get a tmp reg
    movw    r3, #:lower16:.LANCHOR0   @ tmp131,
    mla     lr, r10, r5, r7           @ tmp133, n.1, r, k.2
    movt    r3, #:upper16:.LANCHOR0   @ tmp131,
    mla     ip, r7, r10, r6           @ tmp137, k.2, n.1, c
    ldr     r2, [r3]                  @ src1, src1
    ldr     r0, [r3, #4]              @ src2, src2
    ldr     r1, [r2, lr, lsl #2]      @ *_10, *_10
    ldr     r3, [r0, ip, lsl #2]      @ *_20, *_20
    mla     r4, r3, r1, r4            @ sum, *_20, *_10, sum
    ldr     pc, [sp], #4              @

使用-fcall-used-r8 -fcall-used-r9編譯-fcall-used-r8 -fcall-used-r9沒有幫助; gcc產生與推動lr以獲得額外臨時性相同的代碼。 它無法使用ldmia (多次加載),因為它對將哪個臨時文件放入哪個reg做出了次優選擇。 r0 &src1會將src1src2加載到r2r3 。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM