Rustで作るBinary Translation型のエミュレータ、基本的な機能はかなり備わってきたのだが、ここまで来るとテストパタンを動かしてみたい。RISC-Vのテストベンチとしては代表的なものにriscv-tests
のbenchmarks
ディレクトリのテストパタンが挙げられるが、これを動かすためにはまずはシステムコールをサポートしなければならない。例えば、テストパタンによってはprintf()
が使用されている場合があり、このときはシステムコールのお世話になる必要がある。
riscv-tests
のprintf()
は非常に限定的な実装になっており、put()
がホストマシンに投げられているので非常に単純だ。
riscv-tools/riscv-tests/benchmarks/common/syscalls.c
int printf(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vprintfmt((void*)putchar, 0, fmt, ap);
このputchar()
はシステムコールを呼び出しているだけである。つまり、syscall(SYS_write)
により出力したい文字を書き込んでいる。
#undef putchar int putchar(int ch) { static __thread char buf[64] __attribute__((aligned(64))); static __thread int buflen = 0; buf[buflen++] = ch; if (ch == '\n' || buflen == sizeof(buf)) { syscall(SYS_write, 1, (uintptr_t)buf, buflen); buflen = 0; }
このsyscall
の実体は以下のようになっており、tohost
がアサインされているメモリ領域に対してシステムコールの必要な情報を格納したメモリアドレスを投げ込む、そしてfromhost
が1になるまでそこでスピンして待ち、fromhost
が1になると再度fromhost
を0に戻して終わる。
static uintptr_t syscall(uintptr_t which, uint64_t arg0, uint64_t arg1, uint64_t arg2) { volatile uint64_t magic_mem[8] __attribute__((aligned(64))); magic_mem[0] = which; magic_mem[1] = arg0; magic_mem[2] = arg1; magic_mem[3] = arg2; __sync_synchronize(); tohost = (uintptr_t)magic_mem; while (fromhost == 0) ; fromhost = 0; ...
while loop_idx < loop_max { if self.m_arg_config.debug { println!("========= BLOCK START ========="); } let tb_text_mem = match self.m_tb_text_hashmap.get(&self.m_pc[0]) { Some(mem_map) => { if self.m_arg_config.debug { .... if self.get_mem(0x1000) != 0 { if self.get_mem(0x1000) & 0x01 == 1 { break; } self.sys_write(self.read_mem_8byte(0x80001000)); self.write_mem_4byte(0x80001000, 0); self.write_mem_4byte(0x80001040, 1); }
つまり0x1000
(=0x8000_0000)に0以外の書き込みが発生している場合にシステムコールを呼び出す。システムコールの呼び出しにはsys_write()
を呼び出している、というか現在はこのwrite()
システムコールしかサポートされていない。そもそも実装としても非常に中途半端で、システムコールをサポートするプロセスとメインのループを管理するプロセスを分けるとかなんとかしてならんもんかね。
fn sys_write(&mut self, tohost: u64) { let fd = self.read_mem_8byte(tohost + 8); let pbuf = self.read_mem_8byte(tohost + 16); let len = self.read_mem_8byte(tohost + 24); println!("sys_write() = {:x} ,tohost = {:x}", pbuf, tohost); for idx in 0..len { print!("{}", self.read_mem_1byte(pbuf.wrapping_add(idx)) as char); } }
とりあえず以下のテストコードを動かして確認した。
int main() { printf("Hello, World\n"); return 0; }
$ make -k riscv64-unknown-elf-gcc -I./../env -I./common -I./median -I./qsort -I./rsort -I./towers -I./vvadd -I./multiply -I./mm -I./dhrystone -I./spmv -I./mt-vvadd -I./mt-matmul -I./pmp -I./print -march=rv64g -DPREALLOCATE=1 -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -o print.riscv ./print/print.c ./common/syscalls.c ./common/crt.S -static -nostdlib -nostartfiles -lm -lgcc -T ./common/test.ld
これを動かすと以下のようになった。無事にHello, world
が表示されたのでとりあえずは問題無いようだ。
$ cargo run -- --elf-file /home/msyksphinz/work/riscv/riscv-tools/riscv-tests/benchmarks/print.riscv
sys_write() = 80002c40 ,tohost = 80022940 Hello, World