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