为什么Risc-V的GCC在调用后生成nop指令

7

Risc-V的GCC默认在call指令之后生成nop指令:

$ cat test.c
void g();
void f() {
        g();
}
$ riscv64-unknown-elf-gcc -S test.c -o -    
[...]
f:
        addi    sp,sp,-16
        sd      ra,8(sp)
        sd      s0,0(sp)
        addi    s0,sp,16
        call    g
        nop #### <-----------here
        ld      ra,8(sp)
        ld      s0,0(sp)
        addi    sp,sp,16
        jr      ra
        .size   f, .-f
        .ident  "GCC: (GNU) 8.3.0"

我原本预计在针对具有分支延迟槽的体系结构时,会出现 分支延迟槽,但我的理解是 Risc-V 不属于这种体系结构。实际上,在使用 -O1 或更高级编译选项编译时,nop 指令会消失。
GCC 是否发出 nop 作为具有延迟槽体系结构的剩余部分只是一个“错误”,还是存在某些原因使得需要这个 nop 指令?
2个回答

8
不是完整的答案,但至少尝试深入探讨了nop出现的原因。我强烈认为这是一种来自具有延迟槽架构的bug/剩余物(因为它在第一个RTL pass - expand中被添加)。
关于调查,GCC有两种类型的pass,Tree和RTL。为了看到它们的操作,准备两个文件夹,nooptopt,并使用-fdump-tree-all-raw -fdump-rtl-all来查看中间结果。 Tree pass的最后阶段给出(noopt情况):
$ cat noopt/test.c.232t.optimized

;; Function f (f, funcdef_no=0, decl_uid=1549, cgraph_uid=0, symbol_order=0)

f ()
{
  <bb 2> :
  gimple_call <g, NULL>
  gimple_return <NULL NULL>

}
< p >Opt(-O1)情况稍有不同:

$ diff -u noopt/test.c.232t.optimized opt/test.c.232t.optimized 
--- noopt/test.c.232t.optimized 2019-09-03 14:48:02.874071927 +0200
+++ opt/test.c.232t.optimized   2019-09-03 14:48:29.550278667 +0200
@@ -3,7 +3,7 @@

 f ()
 {
-  <bb 2> :
+  <bb 2> [local count: 1073741825]:
   gimple_call <g, NULL>
   gimple_return <NULL NULL>

RTL的第一个阶段(扩展)与其他阶段不同。
$ cat noopt/test.c.234r.expand

;; Function f (f, funcdef_no=0, decl_uid=1549, cgraph_uid=0, symbol_order=0)


;; Generating RTL for gimple basic block 2


try_optimize_cfg iteration 1

Merging block 3 into block 2...
Merged blocks 2 and 3.
Merged 2 and 3 without moving.
Merging block 4 into block 2...
Merged blocks 2 and 4.
Merged 2 and 4 without moving.


try_optimize_cfg iteration 2



;;
;; Full RTL generated for this function:
;;
(note 1 0 3 NOTE_INSN_DELETED)
(note 3 1 2 2 [bb 2] NOTE_INSN_BASIC_BLOCK)
(note 2 3 5 2 NOTE_INSN_FUNCTION_BEG)
(call_insn 5 2 8 2 (parallel [
            (call (mem:SI (symbol_ref:DI ("g") [flags 0x41] <function_decl 0x7fbc2827a400 g>) [0 g S4 A32])
                (const_int 0 [0]))
            (clobber (reg:SI 1 ra))
        ]) "../test.c":3 -1
     (nil)
    (nil))
(insn 8 5 0 2 (const_int 0 [0]) "../test.c":4 -1
     (nil))

-O1 的不同之处只在于删除了那个 const_int 0 [0],这最终会导致 nop 的出现:

$ diff -u noopt/test.c.234r.expand opt/test.c.234r.expand 
--- noopt/test.c.234r.expand    2019-09-03 14:48:02.874071927 +0200
+++ opt/test.c.234r.expand  2019-09-03 14:48:29.550278667 +0200
@@ -25,12 +25,10 @@
 (note 1 0 3 NOTE_INSN_DELETED)
 (note 3 1 2 2 [bb 2] NOTE_INSN_BASIC_BLOCK)
 (note 2 3 5 2 NOTE_INSN_FUNCTION_BEG)
-(call_insn 5 2 8 2 (parallel [
-            (call (mem:SI (symbol_ref:DI ("g") [flags 0x41] <function_decl 0x7fbc2827a400 g>) [0 g S4 A32])
+(call_insn 5 2 0 2 (parallel [
+            (call (mem:SI (symbol_ref:DI ("g") [flags 0x41] <function_decl 0x7f0bdec1f400 g>) [0 g S4 A32])
                 (const_int 0 [0]))
             (clobber (reg:SI 1 ra))
         ]) "../test.c":3 -1
      (nil)
     (nil))
-(insn 8 5 0 2 (const_int 0 [0]) "../test.c":4 -1
-     (nil))

-1

GCC 在任何架构上都可能会执行此操作。我认为 nop 指令与 void 结果有关,这是非空函数设置返回值的地方。尝试编译此代码:

int g();
int f() {
        g();
        return 1;
}

对于无返回值函数,生成结果时没有任何操作可做,因此使用 nop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接