止まった検査装置の最後の1行
工場の検査装置が、ラインの途中で止まりました。装置が残した記録には、最後に実行した命令が1行だけあります。sw a1, 12(a0) です。
装置の中にソースコードはなく、あるのは命令だけです。命令が読めれば、止まったときにプログラムが何をしていたかが分かります。
このレッスンのゴール
メモ
短い RISC-V のプログラムを読むことができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| レジスタ | プロセッサの中の、小さくて速い記憶場所 | a0 |
| 即値 | 命令の中に直接書かれた数 | 93 |
| ロード | メモリの値をレジスタへ写す命令 | lw |
| ストア | レジスタの値をメモリへ写す命令 | sw |
| 分岐 | プログラムの別の場所へ飛ぶかもしれない命令 | bne |
| ラベル | プログラムの1つの場所に付けた名前 | loop: |
学生が入力するセルは、このプレビューでは空白です。
役目のある32個のレジスタ
64ビットの RISC-V のプロセッサには、64ビットのレジスタが32個あります。名前は x0 から x31 です。アセンブリでは、多くのレジスタをふだんの役目の名前で呼びます。
x0 は zero と呼び、いつ読んでも0です。書きこんでも捨てられます。a のレジスタは関数の入力と答えを運び、t のレジスタは一時的な値を持ちます。
例: このレッスンで会うレジスタ
| 番号 | 名前 | ふだんの役目 |
|---|---|---|
| x0 | zero | いつも0 |
| x2 | sp | スタックの場所を指すスタックポインタ |
| x5 から x7 | t0 から t2 | 一時的な値 |
| x10、x11 | a0、a1 | 関数の入力。答えは a0 |
| x17 | a7 | ecall で Linux に頼むシステムコールの番号 |
学生が入力するセルは、このプレビューでは空白です。
アーキテクチャのフォルダへ移る
cd ~/archここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
命令の3つの形
ほとんどの命令は、まず答えを入れるレジスタを書き、次に入力がどこから来るかを書きます。
add a0, a0, t0 は、2つのレジスタを足します。addi t0, t0, 1 は、レジスタと即値を足します。li a0, 5 は別の形に見えますが、省略形です。小さい数なら、アセンブラは addi a0, zero, 5 と書きます。
ロードとストアがメモリに触れる
レジスタは少ないので、データはメモリに置きます。RISC-V の計算は、レジスタどうしでしか行いません。ロードはメモリからレジスタへ、ストアはレジスタからメモリへ値を写します。
lw a0, 12(a1) は、a1 の番地に12を足した場所から、4バイトを読みます。C の int は4バイトなので、表の4番目(0から数えて3番)は12バイト先です。
loop.c を RISC-V のアセンブリにする
riscv64-linux-gnu-gcc -O2 -march=rv64gc -S loop.cここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ロードとストアを行番号付きで探す
grep -n -E 'lw|sw' loop.sここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
行ごとに足すループを見る
grep -A 4 'L6:' loop.sここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
例: C と RISC-V を並べる
| loop.c の C | RISC-V | 働き |
|---|---|---|
| grid[r][c] = 1; | sw a3,0(a5) | 1 を持つ a3 を、a5 の番地へ書く |
| sum = sum + grid[r][c]; | lw a4,0(a5) | a5 の番地から4バイトを a4 へ読む |
| sum = sum + grid[r][c]; | add a3,a3,a4 | 読んだ数を合計に足す。レジスタの中で |
| 次の c へ | addi a5,a5,4 | 番地を int 1つ分、4バイト進める |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
「add は、メモリの数を直接足せる」と考える人が多くいます。RISC-V ではできません。メモリに触れるのはロードとストアだけです。値はロードし、レジスタの中で計算して、ストアで戻します。だから合計を出すには、まず lw、それから add が要ります。
分岐でループを作る
分岐は2つのレジスタを比べ、条件が合えば飛びます。beq は等しいときに、bne は等しくないときに飛びます。
loop: のようなラベルが、飛び先の名前です。次のプログラムは、8 を5回足します。t0 が回数を数え、a0 が合計を持ちます。bne t0, t1, loop は、t0 が5になるまで上へ戻ります。
RISC-V でループを書く
printf '.globl _start\n_start:\n li a0, 0\n li t0, 0\n li t1, 5\nloop:\n addi a0, a0, 8\n addi t0, t0, 1\n bne t0, t1, loop\n li a7, 93\n ecall\n' > times.sここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
アセンブルする
riscv64-linux-gnu-as times.s -o times.oここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
リンクする
riscv64-linux-gnu-ld times.o -o timesここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
エミュレータで実行する
qemu-riscv64 ./timesここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
合計を読む
echo $?ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
省略形なしで読み戻す
riscv64-linux-gnu-objdump -d -M no-aliases timesここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
さらに挑戦
1 から 9 までの奇数の和を出す RISC-V のプログラムを書いてください。使うのは li、add、addi、bne だけです。t0 を 1 から始めて、2ずつ増やします。
まず自分のインタプリタで動かします。次に、times.s と同じように printf でファイルにし、ラボで組み立てて実行します。終了ステータスは 25 になるはずです。