FPGA開発日記

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

オリジナルLLVM Backendを追加しよう (29. LLVMリグレッションテストの書き方)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

LLVMでのテスト記述とリグレッション

LLVMのオリジナルのバックエンドを実装してきたが、いろんなソースコードコンパイルして、いろんなテストプログラムを動かしてきた。 これだけ多くのテストパタンを実行するのなら、テスト環境とリグレッションテストを用意しておくのが良い。LLVMは、テストパタンとリグレッションテストの環境が用意されいる。 詳細なドキュメントはここを参照してほしいのだが、ここでは、これまで作成したオリジナルバックエンドをテストするための環境について調査する。

llvm-lit

llvm-litは"LLVM Integrated Tester"で、LLVMとClangのテストスイートを実行するためのツールである。 テストセットを記述してLLVMに食わせ、その結果が想定したものかどうかまでチェックしてくれる。

llvm-litは以下のようにして使用する。

$ ./bin/llvm-lit -h
usage: llvm-lit [-h] [--version] [-j N] [--config-prefix NAME] [-D NAME=VAL]
                [-q] [-s] [-v] [-vv] [-a] [-o PATH] [--no-progress-bar]
                [--show-unsupported] [--show-xfail] [--path PATH] [--vg]
                [--vg-leak] [--vg-arg ARG] [--time-tests] [--no-execute]
                [--xunit-xml-output XUNIT_OUTPUT_FILE]
                [--timeout MAXINDIVIDUALTESTTIME] [--max-failures MAXFAILURES]
                [--max-tests N] [--max-time N] [--shuffle] [-i]
                [--filter REGEX] [--num-shards M] [--run-shard N] [--debug]
                [--show-suites] [--show-tests] [--single-process]
                [test_paths [test_paths ...]]

では、コードジェネレータ(LLVMバックエンド)のテストを見てみる。 LLVMリポジトリ内に、test/CodeGen内にテストが入っている。

tree -L 1 test/CodeGen
test/CodeGen
├── AArch64
├── AMDGPU
├── ARC
├── ARM
├── AVR
├── BPF
├── Generic
├── Hexagon
├── Inputs
├── Lanai
├── MIR
├── MSP430
├── Mips
├── NVPTX
├── Nios2
├── PowerPC
├── RISCV
├── SPARC
├── SystemZ
├── Thumb
├── Thumb2
├── WebAssembly
├── WinCFGuard
├── WinEH
├── X86
└── XCore

26 directories, 0 files

ターゲットアーキテクチャ毎に、テストパタンが並んでいる。 さらに、RISCVの中身をのぞいてみる。

tree -L 1 test/CodeGen/RISCV
test/CodeGen/RISCV
├── addc-adde-sube-subc.ll
├── align.ll
├── alloca.ll
├── alu32.ll
├── analyze-branch.ll
├── arith-with-overflow.ll
...
├── tail-calls.ll
├── vararg.ll
├── wide-mem.ll
└── zext-with-load-is-free.ll

0 directories, 76 files

Clangで処理された中間形式LLVM IRのファイル群が格納されている。さらに、test/CodeGen/RISCV/alu32.llを見てみる。

; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s \
; RUN:   | FileCheck %s -check-prefix=RV32I

; These tests are each targeted at a particular RISC-V ALU instruction. Other
; files in this folder exercise LLVM IR instructions that don't directly match a
; RISC-V instruction

; Register-immediate instructions

define i32 @addi(i32 %a) nounwind {
; RV32I-LABEL: addi:
; RV32I:       # %bb.0:
; RV32I-NEXT:    addi a0, a0, 1
; RV32I-NEXT:    ret
  %1 = add i32 %a, 1
  ret i32 %1
}

RUN:の行に、テストの内容が書かれている。 このテストでは、llc -mtriple=riscv32 -verify-machineinstrs < [テストファイル名] | FileCheck [テストファイル名] -check-prefix=RV32Iが実行される、ということになる。

llcで生成したターゲットアセンブリコードを、FileCheckコマンドで結果を比較する、ということになる。 実行してみる。

$ ./bin/llvm-lit ../llvm-myriscvx/test/CodeGen/RISCV/alu32.ll
-- Testing: 1 tests, 1 threads --
PASS: LLVM :: CodeGen/RISCV/alu32.ll (1 of 1)
Testing Time: 0.87s
  Expected Passes    : 1

LLVM IRのコード:

define i32 @addi(i32 %a) nounwind {
  %1 = add i32 %a, 1
  ret i32 %1
}

に対して、以下のコードが生成されることを想定しています。 これが一致すると、テストがPassとなる。

addi:
# %bb.0:
    addi a0, a0, 1
    ret

Cpu0のテストを改造してMYRISCVX用にする

Cpu0のテストを改造して、MYRISCVX用に移植してみる。

  • lbdex/regression-test/Cpu0/seteq.ll
; RUN: llc  -march=cpu0 -mcpu=cpu032I  -relocation-model=pic %s -o - | FileCheck %s -check-prefix=cpu032I
; RUN: llc  -march=cpu0 -mcpu=cpu032II  -relocation-model=pic %s -o - | FileCheck %s -check-prefix=cpu032II
; terminal command (not work): llc  -march=cpu0 -mcpu=cpu032II  -relocation-model=pic %s < %s | FileCheck %s

@i = global i32 1, align 4
@j = global i32 10, align 4
@k = global i32 1, align 4
@r1 = common global i32 0, align 4
@r2 = common global i32 0, align 4

define void @test() nounwind {
entry:
  %0 = load i32, i32* @i, align 4
  %1 = load i32, i32* @k, align 4
  %cmp = icmp eq i32 %0, %1
  %conv = zext i1 %cmp to i32
  store i32 %conv, i32* @r1, align 4

; cpu032I:  cmp $sw, ${{[0-9]+|t9}}, ${{[0-9]+|t9}}
; cpu032I:  andi        $[[T1:[0-9]+|t9]], $sw, 2
; cpu032I:  shr ${{[0-9]+|t9}}, $[[T1]], 1
; cpu032II:  xor        $[[T0:[0-9]+|t9]], ${{[0-9]+|t9}}, ${{[0-9]+|t9}}
; cpu032II:  sltiu      ${{[0-9]+|t9}}, $[[T0]], 1

  ret void
}

どのレジスタアサインされるかわからないので、そこは柔軟に書いてある。${{[0-9]+|t9}} がそれに当たる。これをMYRISCVX用に改造する。 テストコードが配置されているディレクトリにtest/CodeGen/MYRISCVX/ディレクトリを作成する。

  • test/CodeGen/MYRISCVX/seteq.ll
; RUN: llc -march=myriscvx32  -relocation-model=pic %s -o - \
; RUN: | FileCheck %s -check-prefix=myriscvx32

@i = global i32 1, align 4
@j = global i32 10, align 4
@k = global i32 1, align 4
@r1 = common global i32 0, align 4
@r2 = common global i32 0, align 4

define void @test() nounwind {
entry:
  %0 = load i32, i32* @i, align 4
  %1 = load i32, i32* @k, align 4
  %cmp = icmp eq i32 %0, %1
  %conv = zext i1 %cmp to i32
  store i32 %conv, i32* @r1, align 4

; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %hi(_gp_disp)
; myriscvx32: addi    $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], %lo(_gp_disp)
; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %got_hi(k)
; myriscvx32: add     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $3
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], %got_lo(k)($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], 0($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %got_hi(i)
; myriscvx32: add     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $3
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], %got_lo(i)($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], 0($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: xor     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]]
; myriscvx32: sltiu   $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], 1
; myriscvx32: lui     $[[REGISTER:[0-9A-Ba-b_]+]], %got_hi(r1)
; myriscvx32: add     $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]], $[[REGISTER:[0-9A-Ba-b_]+]]
; myriscvx32: lw      $[[REGISTER:[0-9A-Ba-b_]+]], %got_lo(r1)($[[REGISTER:[0-9A-Ba-b_]+]])
; myriscvx32: sw      $[[REGISTER:[0-9A-Ba-b_]+]], 0($[[REGISTER:[0-9A-Ba-b_]+]])

  ret void
}

生成されるコードをざっと書き下してみた。同じようにコードが生成されると成功だ。

$ ./bin/llvm-lit ../llvm-myriscvx/test/CodeGen/MYRISCVX/seteq.ll
-- Testing: 1 tests, 1 threads --
PASS: LLVM :: CodeGen/MYRISCVX/seteq.ll (1 of 1)
Testing Time: 0.29s
  Expected Passes    : 1