FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-Vがリンクレジスタを2つ用意しているのは何故なのか

最近は朝の体操としてRISC-Vの仕様書を一から全部読みなおしている。ジャンプアンドリンク命令の部分を読んでいて、自分でも良く知らないところが出てきたので調べてみた。

RISC-VのABIでは通常ジャンプアンドリンクのリンクレジスタとしてraレジスタ(x1)が使用される。 しかし実はAlternateリンクレジスタとしてt0レジスタも使用可能となっている。これはどういう意味なのか。

f:id:msyksphinz:20210403154032p:plain

そのまま仕様書を読むと、「ミリコード(millicode)」のためにAlternate Registerを定義したと書いてあるがミリコードとは何なのか。 よくよく調べていくと、答えはAndrew Waterman(RISC-V ISAの生みの親)の博士論文に書いてあった。

www2.eecs.berkeley.edu

ミリコードの議論のモチベーションはRVC命令から来ていて、RVC命令に複数レジスタのロードストア命令を含めるかどうか、という所から来ている。 複数レジスタのロードストア命令の利点は静的な(コンパイル時)の命令数を削減することができる点ではあるが、結局のアイデアは見送りになっている。 これには様々な要因があるが、例えばアウトオブオーダ実行のCPUでは複数の操作をまとめた一つの命令を実行するよりも分解していた方が命令のスケジューリングを柔軟にできる。このようなRISC的思想から複数レジスタのロードストア命令は見送られた。

ただ、静的に命令数を削減したいというのならば、関数のプロローグとエピローグを共通化した「ミリコード」が有効だとしている。 関数のプロローグとエピローグは退避するレジスタに応じて共通化できるため、関数間で共通化することができる。例えば以下のような階乗計算プログラムに使用できる。

uint64_t factorial(uint64_t x) {
  if (x > 0)
    return factorial(x - 1) * x;
  return 1;
}

通常ならば、以下のようにしてコンパイルされる。プロローグとエピローグは埋め込まれたままだ。

00: cd11     c.beqz a0, 1c         
02: 1141     c.addi sp, sp, -16    
04: e406     c.sdsp ra, 8(sp)      
06: e022     c.sdsp s0, 0(sp)      
08: 842a     c.mv s0, a0           
0a: 157d     c.addi a0, -1         
0c: ff5ff0ef jal ra, factorial 
10: 02850533 mul a0, a0, s0    
14: 60a2     c.ldsp ra, 8(sp)      
16: 6402     c.ldsp s0, 0(sp)
18: 0141     c.addi sp, 16 
1a: 8082     c.jr ra
1c: 4505     c.li a0, 1
1e: 8082     c.jr ra

一方でミリコードを使うと以下のようになる。prologue_2epilogue_2がミリコードであり、関数間で共通化されたルーチンとなる。 prologue_2はt0を使ってジャンプ&リンクしているが、epilogue_2は末尾再帰を使用してリンクを省略していることに注意。

00: c919     c.beqz a0, 16
02: 016002ef jal t0, prologue_2
06: 842a     c.mv s0, a0
08: 157d     c.addi a0, -1
0a: ff7ff0ef jal ra, factorial
0e: 02850533 mul a0, a0, s0
12: 0100006f jal x0, epilogue_2
16: 4505     c.li a0, 1
18: 8082     c.jr ra

prologue_2:               
00: 1141 c.addi sp, -16   
02: e406 c.sdsp ra, 8(sp) 
04: e022 c.sdsp s0, 0(sp) 
06: 8282 c.jr t0          

epilogue_2:
00: 60a2 c.ldsp ra, 8(sp)
02: 6402 c.ldsp s0, 0(sp)
04: 0141 c.addi sp, 16
06: 8082 c.jr ra

この時にミリコードを呼び出すためのリンクレジスタとして"Alternate Link Register"であるt0が使用されているという訳だ。つまり標準的なABIとしてリンクレジスタではなく、特殊な用途で呼び出すためのリンクレジスタ、ということだ。

なぜこのような技法が使えるかというと、RISC-VのJAL命令はリンクレジスタとして任意のレジスタが使用可能だからだ。MIPSなどではリンクレジスタが固定されているが、RISC-Vではリンクレジスタが固定されていないため、このような技法が適用可能となる。

このミリコードをGCCに実装して評価した結果が以下のようになっている。CPU2006のベンチマークを実行し、テキストサイズと動的命令実行数を評価した。テキストサイズは平均で4%削減されたが、実行命令数は平均で3%増加となっている。

f:id:msyksphinz:20210403161011p:plain