2017年12月11日
いらいざ@Eliza_0x
・大阪工業大学IC科一回生です
・コンパイラとかCPUとか作っています
・今日電車の乗り換えに三回失敗したつらい
・がんばって髪をそめようとした
・今日がんばってワックスをつけてきた
・がんばって髪をそめようとした
・剛毛で染まらなかった
・今日がんばってワックスをつけてきた
・風呂に三日入ってない人みたいな髪型になってしまった…
main := fact 5;
fact :: Int -> Int
fact n := let
iszero n := n = 0;
in if iszero n
then 1
else n * fact (n - 1);
「プログラムバグったっす…」
「コンパイラの気持ちになって考えればいいよ」
ほんまか
といっても、訓練されていない人間に
コンパイラの気持ちはわからない
・バリアフリーについて考えるとき
・車いす体験
・聾者体験
・バリアフリーについて考えるとき
・車いす体験
・聾者体験
・コンパイラを体験してみると良いのでは?
#include <stdio.h>
int main() {
int fact = 1;
int n = 1;
while (n <= 5) {
fact = fact * n;
n = n - 1;
}
}
・以下のコードを手動コンパイルすることを目標にします
・階乗です
・コンパイラの仕事
・ソースコードをコンパイルして機械語を出力
・CPUに出すことができる命令
・プログラムはコンパイル後これに変換される
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010 0002 003e 0001 0000 03e0 0040 0000 0000
0000020 0040 0000 0000 0000 19a8 0000 0000 0000
0000030 0000 0000 0040 0038 0009 0040 001f 001c
0000040 0006 0000 0005 0000 0040 0000 0000 0000
0000050 0040 0040 0000 0000 0040 0040 0000 0000
0000060 01f8 0000 0000 0000 01f8 0000 0000 0000
0000070 0008 0000 0000 0000 0003 0000 0004 0000
0000080 0238 0000 0000 0000 0238 0040 0000 0000
0000090 0238 0040 0000 0000 001c 0000 0000 0000
・機械語は読めない
・そのまま人間が読めるように変換したもの
.L3:
movl -8(%rbp), %eax
imull -4(%rbp), %eax
movl %eax, -8(%rbp)
subl $1, -4(%rbp)
.L2:
cmpl $0, -4(%rbp)
jg .L3
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
・変数はプログラム中に無限に定義できる
・CPUの中には変数に対応するレジスタが複数個ある
・レジスタ0番は常に0であることを要求される
0
1
3
10
0
1
0
レジスタ0番
レジスタ1番
ADD $1 $2 $3
0
3
3
10
5
1
0
0
13
3
5
1
0
10
MUL $4 $5 $6
0
13
3
10
5
5
3
0
13
3
15
5
3
10
先の命令2つは、レジスタとレジスタを演算する命令
a = b + 5
c = 10
c = c - 10
のような演算をしたいときに困る
ADDI $1 $2 5
0
13
3
5
5
1
0
0
8
3
5
1
0
5
ADDI $1 $0 10
0
13
3
5
5
1
0
0
10
3
5
1
0
5
SUBI $3 $0 10
0
13
3
5
5
1
0
0
6
3
5
1
0
-5
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq $3 $0 5
・CPUはメモリにアクセスするための変数をもっている
・PC: プログラム・カウンタと呼ばれる
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq $3 $0 5
PC: 0
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq $3 $0 5
PC: 1
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq $3 $0 5
PC: 2
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq $3 $0 5
PC: 3
・なんども同じことをかくのはめんどくさい
・ならばPCをいじってしまえば良い
・C言語のgotoと対応
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq $3 $0 5
PC: 3→1
・ただジャンプするだけでは芸がない
・beq命令は比較して、同じであればジャンプ
beq $1 $2 10
・上のコードだと、レジスタ1と2が一緒であれば
・PCを10に書き換え
・10番地にジャンプ
addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
beq $1 $2 5
addi $1 $1 1
beq $3 $0 5
if($1==$2){
PC = 5
}
0
5
5
7
・計算が終わったプログラムは停止させたい
halt
int a = 1;
int sum = 0;
int border = 11;
while (a != border) {
sum = sum + a;
a++;
}
return 0;
・Cのプログラム例
0: addi $1 $0 1
1: addi $2 $0 0
2: addi $3 $0 11
3: beq $1 $3 7
4: add $2 $2 $1
5: addi $1 $1 1
6: jump 3
7: halt
・アセンブリでの解答例
0: addi $1 $0 1
1: addi $2 $0 0
2: addi $3 $0 11
3: beq $1 $3 7
4: add $2 $2 $1
5: addi $1 $1 1
6: jump 3
7: halt
int a = 1;
int sum = 0;
int border = 11;
while (a != border) {
sum = sum + a;
a++;
}
return 0;
・すなおにそのまま翻訳するだけ!
0: addi $1 $0 1
1: addi $2 $0 0
2: addi $3 $0 11
int a = 1;
int sum = 0;
int border = 11;
・aを$1
・sumを$2,
・borderを$3に割り当てる
・addiで初期化するだけ
3: beq $1 $3 7
...
6: jump 3
while (a != border) {
...
}
・a != border であればループ継続なので、同じであればループ脱出
・PC: 6まで進むとループはじめまで復帰
4: add $2 $2 $1
5: addi $1 $1 1
sum = sum + a;
a++;
・これはやるだけ
0: addi $1 $0 1
1: addi $2 $0 0
2: addi $3 $0 11
3: beq $1 $3 7
4: add $2 $2 $1
5: addi $1 $1 1
6: jump 3
7: halt
int a = 1;
int sum = 0;
int border = 11;
while (a != border) {
sum = sum + a;
a++;
}
return 0;
gcc -O0 -S -o fact.s fact.c
コンパイルしてアセンブリを吐かせてみる
cmpl $0, -4(%rbp)
jg .L3
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
一部抜粋(fact.s)
int a = 5;
int fact = 1;
while (a > 0) {
fact = fact * a;
a = a - 1;
}
return 0;
暇なときちょっと考えてみてください
・アセンブリは意外とシンプル
・コンパイラの気持ちわからんでもない
・一緒にコンパイラつくりませんか?