kivantium活動日記

プログラムを使っていろいろやります

アセンブリ言語 その1

この記事はkivantium Advent Calendarの18日目です。

CPUを設計する際に重要な要素の一つが命令セットアーキテクチャ (ISA) です。 これから設計する簡単なCPUのISAとして、分かりやすく参考文献も多いMIPSを採用することにしました。 MIPS ISA の詳細は https://www.cs.cornell.edu/courses/cs3410/2008fa/mips_vol2.pdf が詳しいです。

CPUの構成

昨日の記事で見たように歴史上様々なCPUが存在しており、CPUはこういう形だと言い切るのは難しいのですが、現在生き残っている多くのCPUはレジスタマシンになっています。

レジスタマシンを構成する主な要素は

プログラムカウンタ (PC)
現在行っている命令の番号を保持します
命令メモリ
CPUが実行する命令を保持します
汎用レジスタ
計算する値や計算結果を格納します
ALU
汎用レジスタの値や命令で指定された値(即値)を入力として受け取り、命令によって指定された計算を行います
データメモリ
汎用レジスタに入りきらないデータの格納や外部との通信などを行います

となっています。

(注: 実際にはデータメモリと命令メモリは同じものであることが多かったりしますが、概念を理解するために詳細は省きます)

これらの要素に対する操作を直接記述していくのが機械語によるプログラムで、バイナリで記述されます。バイナリでプログラムを書くのは人間には厳しいので機械語に対応する命令列をアルファベットで記述できるようにしたのがアセンブリ言語です。

アセンブリ命令の種類

以後しばらくの実装に使う命令セットを種類ごとに紹介します。

演算命令

レジスタ同士の加算やANDなどのいわゆる演算っぽい命令です。

命令 記法 処理
addiu addiu rt, rs, immediate R[rt] ← R[rs] + SignExtImm
addu addu rd, rs, rt R[rd] ← R[rs] + R[rt]
and and rd, rs, rt R[rd] ← R[rs] & R[rt]
or or rd, rs, rt R[rd] ← R[rs] | R[rt]
sll sll rd, rt, shamt R[rd] ← R[rt] << shamt
srl srl rd, rt, shamt R[rd] ← R[rt] >> shamt
slt slt rd, rs, rt R[rd] ← (R[rs] < R[rt]) ? 1 : 0
subu subu rd, rs, rt R[rd] ← R[rs] - R[rt]
  • R[rt]は「rtで指定された番号の汎用レジスタ」を意味します。
  • SignExtImmは指定された即値の符号拡張(最上位bitを必要なだけコピーして付け加えたもの)を表します。

ロード・ストア命令

データメモリへの格納を行う命令です。

命令 記法 処理
lw lw immediate(rs) R[rt] ← Memory[R[rs] + SignExtImm]
sw sw immediate(rs) Memory[R[rs] + SignExtImm] ← R[rt]
  • Memory[R[rs] + SignExtImm]は「rsで指定された番号の汎用レジスタに入っている値に符号拡張した即値を足した値をアドレスに持つメモリ」を意味します。

ジャンプ命令

次の命令のアドレスを指定してプログラムカウンタを書き換える命令です。

命令 記法 処理
j j address PC ← {(PC+4)[31:28], address, 00}
jal jal address PC ← {(PC+4)[31:28], address, 00}; R[31] ← (PC+4)
jr jr rs PC ← R[rs]

条件分岐命令

条件が成立したかどうかに応じて次の命令が変わる命令です。if文やfor文の実装などに不可欠な命令です。

命令 記法 処理
beq beq rt, rs, immediate if(R[rt]==R[rs]) PC ← (PC+4) + (SignExtImm<<2)
bne bne rt, rs, immediate if(R[rt]!=R[rs]) PC ← (PC+4) + (SignExtImm<<2)

命令フォーマット

これらの命令はアセンブリ言語として書いた後、アセンブラというソフトでバイナリ列に変換する必要があります。ここで扱うMIPS 32の場合全ての命令は32bitの値として表されます。フォーマットにはR, I, Jの3種類あります。

R format

汎用レジスタ2つを演算して汎用レジスタに格納する命令はR formatになります。

命令 op rs rt rd shamt funct
addu 000000 XXXXX XXXXX XXXXX 00000 100001
and 000000 XXXXX XXXXX XXXXX 00000 100100
jr 000000 XXXXX 00000 00000 00000 001000
nor 000000 XXXXX XXXXX XXXXX 00000 100111
or 000000 XXXXX XXXXX XXXXX 00000 100101
sll 000000 00000 XXXXX XXXXX XXXXX 000000
srl 000000 00000 XXXXX XXXXX XXXXX 000010
slt 000000 XXXXX XXXXX XXXXX 00000 101010
subu 000000 XXXXX XXXXX XXXXX 00000 100011

I format

汎用レジスタ2つと即値に対する処理を行う命令はI formatになります。

命令 op rs rt immediate
addiu 001001 XXXXX XXXXX XXXXXXXXXXXXXXXX
beq 000100 XXXXX XXXXX XXXXXXXXXXXXXXXX
bne 000101 XXXXX XXXXX XXXXXXXXXXXXXXXX
lw 100011 XXXXX XXXXX XXXXXXXXXXXXXXXX
sw 101011 XXXXX XXXXX XXXXXXXXXXXXXXXX

J format

汎用レジスタを指定せずに長い即値を使う命令はJ formatになります。

命令 op address
j 000010 XXXXXXXXXXXXXXXXXXXXXXXXXXX
jal 000011 XXXXXXXXXXXXXXXXXXXXXXXXXXX

レジスタの使い方

MIPSには汎用レジスタが32個あり、以下のように使い分けることになっています。

名称 番号 用途 呼び出された側が内容を保存する必要があるか?
$zero $0 読み出すと常に0 N/A
$at $1 アセンブラが一時的に使用 No
$v0-$v1 $2-$3 関数の戻り値や式を評価した結果 No
$a0-$a3 $4-$7 関数の引数 No
$t0-$t7 $8-$15 一時変数 No
$s0-$s7 $16-$23 一時変数だがセーブされる Yes
$t8-$t9 $24-$25 一時変数 No
$k0-$k1 $26-$27 OSのカーネル用に予約 No
$gp $28 広域(グローバル)ポインタ Yes
$sp $29 スタックポインタ Yes
$fp $30 フレームポインタ Yes
$ra $31 リターンアドレス N/A

次回はこれらの命令をつかってアセンブリ言語でどのようにプログラムを書くのか見ていきます。