おさらい
エイリアスとは
PPCやMIPSはあまり命令の数がない。ので、他のやつで組み合わせでできるものは他のもので表現してしまおう
PowerPCでは
- liはload immdiate
- addiの特殊なケースがli
- r0は値はゼロとして扱うので、ゼロに何を足してもそのままの値となる
- li命令を別に作ると命令サイズを逼迫するのでaddiでどうにかしよう、という考え方
addi r3,r0,xxxxx li r3, xxxx
MIPSでは
- i=immidiate
- v0 ゼロレジスタ v0 = 0 + xxxx
- 足し算命令のひとつ特殊な場合をliとみなしている
- 数字をレジスタとして代入する
addiu v0, zero, xxxx li v0, xxxx
レジスタからレジスタへ
PowerPC
orの第2オペランドと第3オペランドが等しいとき
or r3,r4,r4 mr r3,r4
MIPS
- adduは即値ではないとき
- ゼロを足すだけだとあまり意味がないので、move扱いとする
addu v0,a0,zero move v0,a0
特別な命令を増やさないで他の命令の一部にのっかる感じで。
6.5.1 nop命令もエイリアスになっている
MIPSでは逆アセンプルの際にエイリアスの変換をせずに元の命令をそのまま出力するオプションがあります(PowerPCにはそういうオプションがないかもしれない)
00fe1400 <null>: fe1400: 03e00008 jr $31 fe1404: 00000000 sll $0,$0,0xO 00fe1408 <return_zero>: fe1408: 03e00008 jr $31 fe140c: 00001021 addu $2,$0,$0 00fe1410 <return_one>: fe1410: 03e00008 jr $31 fe1414: 24020001 addu $2,$0,1
たしかにmoveやliがadduになっている
比較
00fe1400 <null>: fe1400: 03e00008 jr ra fe1404: 00000000 nop 00fe1408 <return_zero>: fe1408: 03e00008 jr ra fe140c: 00001021 move v0,zero 00fe1410 <return_one>: fe1410: 03e00008 jr ra fe1414: 24020001 li v0,1
6.5.2 sll命令とはなにか
この「sll」という命令は,いったい何なのでしょうか? ちょっと調べてみましょう.
fe1404: 00000000 sll $0,$0,0xO fe1404: 00010000 sll $0,$1,0xO fe1404: 00000100 sll $0,$0,0x4
6.5.3 sll命令はシフト演算を行う命令
実は sllはシフト命令で,即値で指定されたビット数ぶんシフト演算を行う。
sll $0,$0,0x0
事実上なにもしない。ゆえにnop これがnopの正体。
命令は32ビットしかないので、32ビット以上シフトすると中身が消滅する
6.5.4 PowerPCでは nopは ori命令になっている
PowerPCでは, nopの実体は何
ここでおさらい。c言語をコンパイルしてアセンブラsファイルを出して、sファイルをアセンブルしてオブジェクトファイルoを出し、オブジェクトファイルoをリンクして実行ファイルxを出し、xファイルを逆アセンブルしてdファイルを出す。
- コンパイル
- アセンブル
- リンク
- 逆アセンブル
sファイルはコンパイラが出力したおまじないがおおくて読みにくい
バイナリは仮のものと最終品の2つあるといってよい。
sファイルが作り直されると、他のファイルも作り直される。
途中でnopを仕込んでmakeしたやつの逆アセンブルファイル
nopとoriは似ている。
00fe143c <return_long>: fe143c: 3c 60 77 88 lis r3,30600 fe1440: 60 63 99 aa ori r3,r3,39338 fe1444: 4e 80 00 20 blr
nopは先頭ノ守イトの「60」以外はオールゼロになっています. ということはnopの正体はオペランドがすべてゼロの ori命令で,「ori rO, rO, OxOOOO」のようなものになっていると推測できる
ori r0,r0,0 ! r0 = r0 | 0 nop
こうしてみると、ori r0,r0,0は何もしていない=nopなのがわかる
あんまり深い意味はない。
6.6 まとめ
- 固定長命令の RISCプロセッサではエイリアスにより命令数や回路規模を節約
- エイリアスは CPUのドキュメントの命令一覧などには載っていない
- その場合は別命令のエイリアスになっているかどうか探してみる
7 メモリ操作
コンビュータの目的は「データ処理」にある。=「CPUが直接扱える」=「機械語命令によって直接アクセスできる」
メモリ操作はよくわかっていない
7.1 PowerPCのメモリ操作
sample.cを読み進めると, load()/store()という関数があります
メモリからレジスタに値をコピーすることを一般にロード(load)、その逆にレジスタの値をメモリに書き込むことをストア(store)と呼ぶ。
とりあえずc言語を見てみよう。
メモリのp番目を呼び出すのがload メモリに直接値を書き込んだりメモリの値を呼び出したりできる。
// p番目のメモリの中身を呼び出す int load(volatile int *p) { return *p; } // p番目のメモリに値を書き込む void store(volatile int *p) { *p = 0xff; }
PowerPC
- lwzがロードの命令で、lがloadの意味なのでは
- 0(r3)の0ってなんなの
00fe149c <load>: fe149c: 80 63 00 00 lwz r3,0(r3) ! r3レジスタの指す先のメモリの値をロードして r3レジスタに格納する fe14a0: 4e 80 00 20 blr 00fe14a4 <store>: fe14a4: 38 00 00 ff li r0,255 fe14a8: 90 03 00 00 stw r0,0(r3) ! r3レジスタの指す先に Oxffが代入 fe14ac: 4e 80 00 20 blr
lwzとstwで命令の向きが変わっている。stw r0,255みたく即値を直接アドレスに入れる命令はPowerPCにはないみたい。なのでliでr0に値を入れてから、r0の中身をr3に入れている
lwzとstwで命令の中身が大きく違う。
7.1.2 構造体のメンバへのアクセス
構造体へのメンバへのアクセス
// 中身は知らない struct structure { int a; int b; int c; }; // 受け取る引数は構造体へのポインタ(アドレス) int member(struct structure *p) { p->b = 1; // stw return p->c; // lwz }
PowerPC
構造体のアセンブラはなくって、関数から呼び出されないと逆アセンブルされない。
00fe14c8 <member>: fe14c8: 38 00 00 01 li r0,1 fe14cc: 90 03 00 04 stw r0,4(r3) fe14d0: 80 63 00 08 lwz r3,8(r3) fe14d4: 4e 80 00 20 blr
「p→b」や「p->c」は構造体のメンバ。これらのメンバはすべてint型なので、「pー>b」は構造体の先頭から 4バイト、「p一>c」は 8バイトの場所にあるはず。
なので4バイトずつディスプレースメントされている。
オフセットを加算したアドレスに対して直接読み書き
0(r3)=(r3+0) 4(r3)=(r3+4) 8(r3)=(r3+8)
こういうふうに書けば、次のアドレスにアクセスできる。
7.1.3 アドレッシングモード
レジスタからアドレス値を得る方法(アドレッシングモード) P118
ここでは、メモリの指定の仕方みたいな意味合いで考えてみると良いかもしれない。
ディスプレースメント付きレジスタ間接 0(r3)みたいな書き方のこと。
なので、disp(r3) = (r3 + disp) みたいな書き方をする。
レジスタ間接 r3 = r3
インデックスつきレジスタ間接 r0(r3)
4r0(r3)みたいな指定ができるCPUがある。(r3+4r0) 命令で演算しなくてもレジスタ同士で演算してたりする。
PowerPCからすごい飛躍してるな。
- w 4バイト
- h 2バイト
- b 1バイト
みたいなくくりが一般共通じゃなかったりする。
7.1.4 静的変数のアドレスの格納
プログラム自体に最初からある変数のことを静的変数という。で、このC言語はこの静的変数をいろいろとごにょごにょしている。
int static_value = 10; long static_long = 0x12345678; int *get_static_value_addr() { return &static_value; } int get_static_value() { return static_value; } void set_static_value(int a) { static_value = a; }
PowerPC
00fe14d8 <get_static_value_addr>: fe14d8: 3c 60 00 fe lis r3,254 fe14dc: 38 63 18 00 addi r3,r3,6144 fe14e0: 4e 80 00 20 blr 00fe14e4 <get_static_value>: fe14e4: 3d 20 00 fe lis r9,254 fe14e8: 80 69 18 00 lwz r3,6144(r9) fe14ec: 4e 80 00 20 blr 00fe14f0 <set_static_value>: fe14f0: 3d 20 00 fe lis r9,254 fe14f4: 90 69 18 00 stw r3,6144(r9) fe14f8: 4e 80 00 20 blr
変数がどこにあるのか、っていう。
変数もどこかのアドレスに格納されている。
static_valueはOx00fe1800というアドレスに配置されているようだ。
0x00fe1800を探してみよう。
00fe14d8 <get_static_value_addr>: fe14d8: 3c 60 00 fe lis r3,254 fe14dc: 38 63 18 00 addi r3,r3,6144 fe14e0: 4e 80 00 20 blr
大きな即値は複数回の命令に分けて格納している。ここではlisとaddi
(lisは0x00fe0000をr3に代入しているのであって、0x00feだけr3に入れているわけではない)
なので、r3 = 0x00fe0000 + 0x00001800 というイメージ
メモリがあってそこから出し入れしている。何番目のメモリを取り出しているか。
急に難しくなっているけど、安心してください。何度も説明があるらしいので。
7.1.5 静的変数へのアクセス
00fe14e4 <get_static_value>: fe14e4: 3d 20 00 fe lis r9,254 fe14e8: 80 69 18 00 lwz r3,6144(r9) ! ディスプレースメントつきレジスタ間接 6144=0x1800 fe14ec: 4e 80 00 20 blr
「メモリ上のアドレス Ox00fe1800に置いてある値を r3レジスタにロードして返る」 r3にたいしてr9の値を読みこめばいい。
- r9=0x00fe0000
- r3=(r9+0x1800)
ディスプレースメント付きレジスタ間接は命令数の削減を目的としている。
別解
でもこの方法だと3命令必要になるので、省メモリ化を図っている。
書き込み。lisしてstwしてるので、ぶっちゃけlwzと同じ。
00fe14f0 <set_static_value>: fe14f0: 3d 20 00 fe lis r9,254 fe14f4: 90 69 18 00 stw r3,6144(r9) fe14f8: 4e 80 00 20 blr
変数はメモリにあるのがポイント。
7.2 MIPSでのメモリ操作
復習をかねたMIPS
7.2.1 口ード/ストアはPowerPCと似ている
MIPS
とりあえずレ点の遅延スロットは無視したらいい。くらいで、命令は割と似ている。が、静的変数の扱い方はちょっと違う。
00fe14ec <get_static_value_addr>: fe14ec: 03e00008 jr ra fe14f0: 2782fff4 addiu v0,gp,-12 00fe14f4 <get_static_value>: fe14f4: 8f82fff4 lw v0,-12(gp) fe14f8: 03e00008 jr ra fe14fc: 00000000 nop 00fe1500 <set_static_value>: fe1500: 03e00008 jr ra fe1504: af84fff4 sw a0,-12(gp)
PowerPC
00fe14d8 <get_static_value_addr>: fe14d8: 3c 60 00 fe lis r3,254 fe14dc: 38 63 18 00 addi r3,r3,6144 fe14e0: 4e 80 00 20 blr 00fe14e4 <get_static_value>: fe14e4: 3d 20 00 fe lis r9,254 fe14e8: 80 69 18 00 lwz r3,6144(r9) fe14ec: 4e 80 00 20 blr 00fe14f0 <set_static_value>: fe14f0: 3d 20 00 fe lis r9,254 fe14f4: 90 69 18 00 stw r3,6144(r9) fe14f8: 4e 80 00 20 blr
MIPSのほうが読みやすい。
7.2.2 グローバルポインタ
ここは難しい。
gp=グローバルポインタで、読み書きコストを下げるワザ。
00fe14f4 <get_static_value>: fe14f4: 8f82fff4 lw v0,-12(gp) fe14f8: 03e00008 jr ra fe14fc: 00000000 nop 00fe1500 <set_static_value>: fe1500: 03e00008 jr ra fe1504: af84fff4 sw a0,-12(gp)
こういう読み方でもいけるけど、r9を使いまわしているので、なんどもr9に代入する必要ないよねっていう話。
というか、0x00fe0000を何度も使いまわすのがわかっているなら、グローバルなレジスタに値を代入するだけでいいんじゃないのっていうのがグローバルポインタの考え方
MIPSはグローバルポインタが標準で利用される。というわけで、予めグローバルポインタをmainの前で定義しておく必要がある。
P743
(PowerPCも特殊オプションでグローバルポインタを使えちゃう)
遅延スロットはレ点。(登山)あと、nopは新しい規格(mips32)を利用することで消える。
load命令はメモリからとってくる。メモリからとってくる作業は時間がかかるので、遅延スロットはうまくいかないことがあるかもしれない。
疑問
-12って結局なんだよ
グローバルポインタはグローバル変数のど真ん中を指しており、そこを基準に+とか-とか判断している。
0x10000 - 0x00001 = 0xffff
数の循環、符号計算。4桁で計算すると
0x0000 - 0x0001 = 0xffff
みたいな循環になる。
-0x8000〜0x7fff マイナスの部分も有効活用しないとマイナス部分が無駄になってしまう。