コンパイラ

気持ち入門

2017年12月11日

いらいざ@Eliza_0x

牧野研の民

です

牧野研はいいぞ

牧野研はいいぞ

自己紹介

ところで電車の乗り換えって

難しくないですか?

・大阪工業大学IC科一回生です

・コンパイラとかCPUとか作っています

・今日電車の乗り換えに三回失敗したつらい

Twitter: @Eliza_0x

私をフォローすると進捗が約束されます

チヤホヤされて〜

・がんばって髪をそめようとした

 

・今日がんばってワックスをつけてきた

チヤホヤされて〜

・がんばって髪をそめようとした

    ・剛毛で染まらなかった

・今日がんばってワックスをつけてきた

    ・風呂に三日入ってない人みたいな髪型になってしまった…

最近作ったもの

自作プログラミング言語 tmpla

main := fact 5;

fact :: Int -> Int
fact n := let
    iszero n := n = 0;
    in if iszero n 
        then 1
        else n * fact (n - 1);

自作CPU: mins

隊長「ところで君から技術

とったらなにが残るん?」

全然気にしてません

フォローしてね

Twitter@Eliza_0x

 本題

よくある会話

「プログラムバグったっす…」

「コンパイラの気持ちになって考えればいいよ」

コンパイラ

気持ちに

なって

考えれば

いいよ

ほんまか

といっても、訓練されていない人間に

コンパイラの気持ちはわからない

問題点

・バリアフリーについて考えるとき

  ・車いす体験

  ・聾者体験

解決策

・バリアフリーについて考えるとき

  ・車いす体験

  ・聾者体験

解決策

・コンパイラを体験してみると良いのでは?

コンパイラ

を体験して

みると良い

のでは?

やってみよう

#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

アセンブリ言語

難しそう?

だいたいC言語の

ノリで読めます

レジスタ

変数はプログラム中に無限に定義できる

・CPUの中には変数に対応するレジスタが複数個ある

・レジスタ0番は常に0であることを要求される

0

1

3

10

0

1

0

レジスタ0番

レジスタ1番

レジスタ

変数のようなもの

として理解してください

加算命令: ADD

ADD $1 $2 $3

0

3

3

10

5

1

0

0

13

3

5

1

0

10

加算命令: MUL

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

ADDI $1 $2 5

0

13

3

5

5

1

0

0

8

3

5

1

0

5

代入: $1 = 10

ADDI $1 $0 10

0

13

3

5

5

1

0

0

10

3

5

1

0

5

即値減算命令: SUBI

SUBI $3 $0 10

0

13

3

5

5

1

0

0

6

3

5

1

0

-5

簡単ですね

 メモリは配列のようなもの

MEMORY

addi $1 $0 5
sub  $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq  $3 $0 5

INSTRUCTION

プログラムカウンタ

・CPUはメモリにアクセスするための変数をもっている

・PC: プログラム・カウンタと呼ばれる

 メモリは配列のようなもの

MEMORY

addi $1 $0 5
sub  $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq  $3 $0 5

PC: 0

 メモリは配列のようなもの

MEMORY

addi $1 $0 5
sub $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq  $3 $0 5

PC: 1

 メモリは配列のようなもの

MEMORY

addi $1 $0 5
sub  $1 $4 $1
addi $4 $5 5
jump 1
addi $1 $1 1
beq  $3 $0 5

PC: 2

 メモリは配列のようなもの

MEMORY

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と対応

 jump

MEMORY

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番地にジャンプ

条件分岐

MEMORY

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

終了

ためしに今紹介した命令で1..10までの和を計算するプログラムを書いてみましょう

1..10の総和

int a = 1;
int sum = 0;
int border = 11;
while (a != border) {
    sum = sum + a;
    a++;    
}
return 0;

・Cのプログラム例

1..10の総和

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

アセンブリでの解答例

1..10の総和

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;

暇なときちょっと考えてみてください

今日からお前がコンパイラだ

・アセンブリは意外とシンプル

・コンパイラの気持ちわからんでもない

・一緒にコンパイラつくりませんか?

deck

By eliza0x

deck

  • 647