FPGA開発日記

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

Binary Translation型エミュレータを作る(シフト命令の実装)

Binary Translation型エミュレータにおいてシフト命令を実装した。Binary Translation型エミュレータでは、ゲストマシンの命令(今回はRISC-V)からホストマシンの命令(今回はx86)に直接変換することで高速実行を可能にするシミュレータだ。

現在実装しているのはシフト命令だ。RISC-Vで取り扱う必要のあるシフト命令は以下の6種類だ。

  • SLL : 左論理シフト命令(シフト量はレジスタ指定)
  • SRL : 右論理シフト命令(シフト量はレジスタ指定)
  • SRA : 右算術シフト命令(シフト量はレジスタ指定)

  • SLLI : 左論理シフト命令(シフト量は即値指定)

  • SRLI : 右論理シフト命令(シフト量は即値指定)
  • SRAI : 右算術シフト命令(シフト量は即値指定)

x86のシフト命令について調査

そしてこれらをx86にどのように変換すればよいかをチェックするために以下のようなC言語のプログラムを用意した。

uint64_t sll_64(uint64_t a, uint64_t b)
{
  return a << b;
}

uint64_t srl_64(uint64_t a, uint64_t b)
{
  return a >> b;
}

int64_t sra_64(int64_t a, uint64_t b)
{
  return a >> b;
}
0000000000000068 <sll_64>:
...
  7e:   89 d1                   mov    %edx,%ecx
  80:   48 d3 e0                shl    %cl,%rax

0000000000000085 <srl_64>:
...
  9b:   89 d1                   mov    %edx,%ecx
  9d:   48 d3 e8                shr    %cl,%rax

00000000000000a2 <sra_64>:
...
  b8:   89 d1                   mov    %edx,%ecx
  ba:   48 d3 f8                sar    %cl,%rax

シフト命令はそれぞれSHL, SHR, SAR命令が定義されている。そしてシフト量はCLつまりECXの下位8ビットで指定するようだ。一応これらの命令がどのような構成になっているのか前回導入したCapstoneで確認してみよう。

cstool -d x64 "48 d3 e0"
 0  48 d3 e0                                         shl        rax, cl
        Prefix:0x00 0x00 0x00 0x00
        Opcode:0xd3 0x00 0x00 0x00
        rex: 0x48
        addr_size: 8
        modrm: 0xe0
        disp: 0x0
        sib: 0x0
        op_count: 2
                operands[0].type: REG = rax
                operands[0].size: 8
                operands[0].access: READ | WRITE
                operands[1].type: REG = cl
                operands[1].size: 1
                operands[1].access: READ
        Registers read: cl rax
        Registers modified: rflags rax
        EFLAGS: MOD_CF MOD_SF MOD_ZF MOD_PF MOD_OF UNDEF_AF

なるほど。構成としては、まずはREXを64ビットモードに設定して64ビット演算をするように設定して、Opcodeは0xd3を設定する。0xd3はシフトとかローテートとかが定義されているものだ。

f:id:msyksphinz:20200914005611p:plain

Mod/RMにおいて、regフィールドを4/5/6に設定することで算術・論理・右左シフトを設定できるようになる。このルールに従って命令を生成すればよい。

一方で即値指定の場合はどうなるのか。

uint64_t slli_64(uint64_t a)
{
  return a << 17;
}

uint64_t srli_64(uint64_t a)
{
  return a >> 17;
}

int64_t srai_64(int64_t a)
{
  return a >> 17;
}
00000000000000bf <slli_64>:
...
  cb:   48 c1 e0 11             shl    $0x11,%rax

00000000000000d1 <srli_64>:
...
  dd:   48 c1 e8 11             shr    $0x11,%rax

00000000000000e3 <srai_64>:
...
  ef:   48 c1 f8 11             sar    $0x11,%rax

今度は0xd3ではなく0xc1を使用しているようだ。こちらも同じようなルールで生成できるらしい。

f:id:msyksphinz:20200914005717p:plain

これらのルールを使ってx86命令を生成するようにコードを組み上げた。

テストの実行と確認

という訳で上記の6種類のRISC-Vシフト命令を実装したので確認する。以下のようなテストパタンを作成した。

_start:
    addi    x1, x0, 11
    lui     x10, 0xdeadb
    addi    x10, x10, 0x3ef

    srl     x15, x10, x1
    sll     x16, x10, x1
    sra     x17, x10, x1

    srli    x18, x10, 11
    slli    x19, x10, 11
    srai    x20, x10, 11

    slli    x21, x10, 32
    srai    x22, x21, 32
    srli    x23, x21, 32

    ret

レジスタ指定と即値指定の両方のシフト命令を確認するほか。32ビットのデータを32ビット左シフトして最上位ビットを1に設定し、もう一度右シフトすることで、算術シフトと論理シフトで結果が異なるの確認する。実行後のレジスタをダンプすると以下のようになった。

x00 = 0000000000000000  x01 = 000000000000000b  x02 = 0000000000000000  x03 = 0000000000000000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 00000000deadb3ef  x11 = 0000000000000000
x12 = 0000000000000000  x13 = 0000000000000000  x14 = 0000000000000000  x15 = 00000000001bd5b6
x16 = 000006f56d9f7800  x17 = 00000000001bd5b6  x18 = 00000000001bd5b6  x19 = 000006f56d9f7800
x20 = 00000000001bd5b6  x21 = deadb3ef00000000  x22 = ffffffffdeadb3ef  x23 = 00000000deadb3ef
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000

想定通りの結果が確認できた。問題無いようだ。