Skip to content

Instantly share code, notes, and snippets.

@serihiro
Last active December 23, 2021 00:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save serihiro/19c9f8ea0584f1bd885cf17ecd29850a to your computer and use it in GitHub Desktop.
Save serihiro/19c9f8ea0584f1bd885cf17ecd29850a to your computer and use it in GitHub Desktop.
パタヘネ 2.10 32ビットの即値およびアドレスに対するMIPSのアドレシング方式
  • MIPSの命令は32ビット固定であるがゆえに32ビットの定数やアドレスを格納することはできない
  • しかし扱えた方が便利な場合がある。そのMIPSにおける解決策を示す

32ビットの即値オペランド

  • 定数は大体が16ビットに収まる(表現できる)が、超えることもある
  • MIPSには16ビットを超える定数の上位16ビットを指定したレジスタの上位16bitに格納するための命令 lui がある
  • 命令フォーマットは Iフィールド で OP(6) + rs(5) + rt(5) + 即値16ビット

例題

下記32ビットの定数をレジスタ $s0 にロードするMIPSアセンブリコードを示せ

0000 0000 0011 1101 0000 1001 0000 0000

いきなり32ビットの定数は扱えないのでまず上位16ビットのみを $s0 にコピーする

lui $s0, 61 # 上位16ビットの10進数表記

上位16ビットのみがコピーされて $s0 は下記のようになる

0000 0000 0011 1101 0000 0000 0000 0000

次に下位16ビットを追加する。 下位16ビットに対して即値OR演算を行う ori を使用する

ori $s0, $s0, 2304 # 下位16ビットの10進数表記

最終的に $s0 には求める値が下記のように設定される

0000 0000 0011 1101 0000 1001 0000 0000

ちなみに実際に↓のコードをgccでコンパイルして吐き出したアセンブラの該当部分は下記のようになっている

#include <stdio.h>

int main(int argc, char ** argv){
    int bigint = 4000000;
    return 0;
}
    li      $2,3997696                      # 0x3d0000
    ori     $2,$2,0x900

なんか li というのを使ってるが、これは議事命令で、実際に機械語に変換される時は別の命令に変換されるらしい。   しかし足している数値を見ると 0x3d_0000 であり、0x3d は61なので61を上位16ビットに格納しているという挙動は lui $s0, 61 と同じであると思われる。
その下の ori で指定されている即値は10進数でいうと2304(0b0000_1001_0000_0000)なので本文と同じ記述で同じ演算をしている。

ちなみに16ビット定数の最大値である 655365 を代入するcのコードをアセンブラに変換すると

       li      $2,65535                        # 0xffff

となり、ビットを分割せずにそのままロードしていることが分かる。

ハードウェアとソフトウェアのインターフェース

  • 上記のような計算をするにはコンパイラまたはアセンブラのどちらが大きな定数を分割してレジスタ上に設定し直す作業を受け持たなければならない
  • 即値フィールドの大きさに関する制限は、即値をとる命令中の定数の場合だけではなく、ロードとストアの際のメモリ・アドレスを表す場合にも問題になる
  • MIPSではアセンブラが受け持つが、その場合には長い値を作るための一時レジスタが必要となる
  • このことが $at がアセンブラ用に予約されている一つの理由である(他にもあるんかい)

分岐とジャンプにおけるアドレシング

  • もっとも単純なアドレシングはジャンプ命令のものである。この命令形式を J形式 という。opコード6ビットとアドレスフィールド26ビットで構成される。
j 10000 # 10_000番地にジャンプ
  • ジャンプ命令のopコードの値は2で、ジャンプ先のアドレスが10_000,ということを表している
  • beq, bneのような分岐命令の場合、I形式なので分岐用アドレス用には16ビットしか残らない。
bne $s0, $s1, Exit # $s0 != $s1ならばExitへ分岐
  • プログラムのアドレスが16ビットに制限されるとすると、プログラム(多分ここでは処理内容を示すための領域という意味)の大きさは2^16を超えることはできないことになる
  • 一方で32ビットアーキテクチャのマシンにおいてメモリのアドレス自体は32ビットで表現されてる以上、2^16を超える番地のアドレスにもアクセスできる必要がある
  • そこで、分岐先アドレスに加算するレジスタを指定する方法が考え出された
  • その場合分岐命令はプログラムカウンタ値を書きのように計算する
プログラムカウンタ値 = レジスタ値 + 分岐先アドレス
  • こうすると2^32までの大きさのプログラムに条件分岐を適用できる
  • で、分岐先アドレスに加算するレジスタ値をどこに保存するか、というのが問題になる
  • ところで、プログラムカウンタ(PC)のレジスタには現在の命令のアドレスが記憶されているので、PCの値と分岐先アドレス値を足せば現在の命令アドレスから±2^15ワードの範囲で分岐できる(16ビットの2の補数表現とみなす場合のことだと思われる)
  • 一方で、条件分岐はif文やループで使われるため、分岐先は分岐命令の近くにある公算が大きい
  • ほとんどのループやif文は2^16ワードよりも少ないので、この方法で指定の番地に到達できる範囲内にあるため、PCはこの目的に最適である
  • この形式の分岐アドレッシングを PC相対アドレシング と呼ぶ
  • なお、MIPSでは現在の命令のアドレスを基準にするのではなく、次の命令のアドレス(PC + 4)を基準にして相対アドレシングを行う
    • これは「一般的な場合を高速化する」の基準に従うものらしいが、おそらく分岐先が1word先にあったら何も加算せずにアドレスを示せるからとか、少しでも足し込む値を小さくして16ビットでアクセスできる範囲に収めたいとか、そんな理由だと思われる
  • また、MIPSの命令長は4バイトで1wordであるため、PC相対アドレシングにおいて次の命令までのバイト数の代わりに語数を使用する  - これにより、ジャンプ命令中の26ビットフィールドも語アドレスを表し、28ビットのバイトアドレスを表すことを意味する

ハードウェアとソフトウェアのインターフェース

  • bnebeq などの分岐命令のアドレスフィールドは16ビットなので、それ以上遠くにジャンプできない
  • それ以上に遠くに飛びたい場合はアセンブラがジャンプ命令を挿入して置き換えてくれる
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment