クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

構文木をスタックの命令に変える(コード生成)

このレッスンのあとで: 構文木を、スタックマシンの命令に変えられる。

55分のレッスンコマンド9件コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

1時間に1万件の申請

前のレッスンの交通費の規則は、今では1時間に1万件の申請で使われています。そのたびに木をたどるのは、遅すぎます。

そこでシステムは、木を一度だけ、小さな手順の平らなリストに直し、申請ごとにそのリストを実行します。この翻訳が、コンパイラの最後の仕事です。

このレッスンのゴール

メモ

構文木を、スタックマシンの命令に変えられる。

重要な言葉

ことばいみれい
コード生成構文木を命令に変えること
スタック最後に入れた値が最初に出る、値の山
スタックマシン命令がスタックから値を取る機械PUSH 2
バイトコードソフトウェアが動かす、スタックマシンの命令
最適化同じことを、より少ない仕事でするようにコードを変えること-O2
定数畳み込みコンパイルのときに計算を済ませること6 * 7 が 42 になる

学生が入力するセルは、このプレビューでは空白です。

スタックマシンは値をスタックに置く

スタックマシンは、途中の値をスタックに置きます。PUSH 2 は2を一番上に積みます。ADD は上の2つを取り、足して、答えを積みます。

木をスタックの命令にするには、まず子の命令を作り、そのあとで節自身の命令を出します。実行すると、子がそれぞれ値をスタックに残し、節がそれを使います。

例: 2 + 3 * 4 の命令

命令実行したあとのスタック(右が一番上)
PUSH 22
PUSH 32, 3
PUSH 42, 3, 4
MUL2, 12
ADD14

学生が入力するセルは、このプレビューでは空白です。

+ と * のためのコード生成器

コード: python
# コード生成: 子を先に、演算をあとに。
def generate(tree, code):
    if type(tree) == int:
        code.append(["PUSH", tree])
        return
    generate(tree[1], code)
    generate(tree[2], code)
    if tree[0] == "+":
        code.append(["ADD"])
    else:
        code.append(["MUL"])


code = []
generate(["+", ["*", 420, 2], 300], code)
for line in code:
    print(line)
# 木を ["*", ["+", 420, 300], 2] に変えて、もう一度実行しましょう。

Python もスタックマシンを動かす

Python は、実行のたびに木をたどってはいません。木をバイトコード、つまり Python の中のスタックマシンの命令にコンパイルし、それを動かします。

モジュール dis は、ファイルのバイトコードを出します。Run で1行のファイル fare.py を書き、そのバイトコードを見ます。最初の RESUME と最後の2行は、どのファイルにも付くので気にしません。

コンパイラのフォルダに入る

コマンド
cd ~/compiler

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

1行のプログラムを書く

コマンド
echo "total = fare * days + 300" > fare.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

そのバイトコード

コマンド
python3 -m dis fare.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

例: fare.py のバイトコード

バイトコード私たちの命令すること
LOAD_NAME fareLOAD farefare の値を積む
LOAD_NAME daysLOAD daysdays の値を積む
BINARY_OP (*)MUL2つ取り、積を積む
LOAD_CONST 300PUSH 300数 300 を積む
BINARY_OP (+)ADD2つ取り、和を積む
STORE_NAME totalまだない一番上を取り、total に束縛する

学生が入力するセルは、このプレビューでは空白です。

最適化はコンパイルのときに仕事をする

gcc は本物のプロセッサの命令を作ります。スタックのほかにレジスタも使います。-O2 を付けると、最適化もします。

Run でアセンブリを2回書きます。最適化なしと、最適化ありです。そして grep -c -w で、square(7) の答えの49という数がある行を数えます。プログラムのどこにも、49とは書いてありません。

最適化なしのアセンブリ

コマンド
gcc -S -O0 -o plain.s hello.c

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

-O2 のアセンブリ

コマンド
gcc -S -O2 -o fast.s hello.c

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

最適化なしで49のある行

コマンド
grep -c -w 49 plain.s

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

最適化ありで49のある行

コマンド
grep -c -w 49 fast.s

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

Python も定数を畳み込む

コマンド
echo "seconds = 60 * 60 * 24" > day.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

畳み込まれた式のバイトコード

コマンド
python3 -m dis day.py

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

最適化とは、同じ命令を速く動かすことだ、と思いがちです。-O2 の gcc は、仕事そのものを書き換えました。square(7) を自分で計算したので、main は掛け算をしません。プログラムの出力は同じです。することが変わらない限り、最適化はどの命令を変えても構いません。

さらに挑戦: 定数を畳み込む

課題の compile_tree に、定数畳み込みを足しましょう。演算の命令を出す前に、2つの子がどちらもただの数かを調べます。そうなら計算を済ませ、PUSH を1つだけ出します。

下から順に畳み込むと、["+", ["*", 60, 60], 24] は [["PUSH", 3624]] になります。名前を含む木が一部しか畳み込めない理由と、max_depth がどう変わるかを説明しましょう。

このラボとソースについて: コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。