minrubyコンパイラ / 条件分岐を実装する
#MinRuby
from MinRubyコンパイラ
minrubyコンパイラで if などの条件分岐をできるようにしたい。
条件分岐の前に == や < や > などの比較演算子を実装する。ただし、現状のminrubyコンパイラは整数 integer しか取り扱えないため、比較演算子の結果が真の時は 1 を、偽の時は 0 を返すようにする。
比較演算子
== や != や > や < や >= や <= の比較演算子を実装する。
code:diff
commit d138d3c60148811f26529fa3f55f391fa736970c
Author: takashi hatakeyama <takashi.hatakeyama@gmail.com>
Date: Fri Aug 4 20:12:48 2023 +0900
比較演算子を実装
diff --git a/minrubyc.rb b/minrubyc.rb
index 938e649..780db82 100644
--- a/minrubyc.rb
+++ b/minrubyc.rb
@@ -29,7 +29,7 @@ end
def gen(tree, env)
if tree0 == "lit"
puts "\tmov x0, ##{tree1}"
- elsif %w(+ - * /).include?(tree0)
+ elsif %w(+ - * / == != < <= > >=).include?(tree0)
op = tree0
expr1 = tree1
expr2 = tree2
@@ -58,6 +58,24 @@ def gen(tree, env)
puts "\tmul x0, x0, x1"
when "/"
puts "\tsdiv x0, x0, x1"
+ when "=="
+ puts "\tcmp x0, x1"
+ puts "\tcset x0, eq"
+ when "!="
+ puts "\tcmp x0, x1"
+ puts "\tcset x0, ne"
+ when "<"
+ puts "\tcmp x0, x1"
+ puts "\tcset x0, lt"
+ when "<="
+ puts "\tcmp x0, x1"
+ puts "\tcset x0, le"
+ when ">"
+ puts "\tcmp x0, x1"
+ puts "\tcset x0, gt"
+ when ">="
+ puts "\tcmp x0, x1"
+ puts "\tcset x0, ge"
else
raise "invalid operator: #{op}"
end
diff --git a/test.sh b/test.sh
index 084c486..f7cfc25 100755
--- a/test.sh
+++ b/test.sh
@@ -18,6 +18,23 @@ assert() {
fi
}
+# 比較演算
+# 真の場合は1、偽の場合は0を返す
+assert 1 'p(1 == 1)'
+assert 0 'p(1 == 2)'
+assert 0 'p(1 != 1)'
+assert 1 'p(1 != 2)'
+assert 1 'p(1 < 2)'
+assert 0 'p(1 < 1)'
+assert 1 'p(1 <= 2)'
+assert 1 'p(1 <= 1)'
+assert 0 'p(1 <= 0)'
+assert 1 'p(2 > 1)'
+assert 0 'p(1 > 1)'
+assert 1 'p(2 >= 1)'
+assert 1 'p(1 >= 1)'
+assert 0 'p(0 >= 1)'
+
# 変数
assert "10\n20\n30\n" "a = 10; b = 20; c = 30; p a; p b; p c"
ifを実装
ifで条件分岐できるようにした。その他、細々とした修正を行った。
ifのブロックの中で定義したローカル変数もアクセスできるようにした
スタックポインタの値は16バイト単位で増減する必要があった
var_names の実装が適当だったので、適当に修正した(どちらにせよ適当だった)
関数の内部でspが増減することがあるので、ローカル変数へアクセスするときにはspではなくfp(フレームポインタ)からのオフセットでアクセスするようにした
code:diff
diff --git a/minrubyc.rb b/minrubyc.rb
index 780db82..4079524 100644
--- a/minrubyc.rb
+++ b/minrubyc.rb
@@ -1,25 +1,27 @@
require "minruby"
# tree 内の変数名一覧
-def var_names(arr, tree)
+def var_names(tree)
if tree0 == "var_assign"
- arr.include?(tree1) ? arr : arr + [tree1]
+ [tree1]
elsif tree0 == "stmts"
- tmp_arr = arr
+ arr = []
tree1...each do |statement|
- tmp_arr = tmp_arr + var_names(tmp_arr, statement)
+ arr = arr + var_names(statement)
end
- tmp_arr
- else
arr
+ elsif tree0 == "if"
+ var_names(tree2)
+ else
+ []
end
end
-# スタックフレーム上の変数のアドレスをスタックポインタ(sp)からのオフセットとして返す
+# スタックフレーム上の変数のアドレスをフレームポインタ(fp)からのオフセットとして返す
# 例:
-# ひとつ目の変数のアドレス = スタックポインタ + 16
-# ふたつ目の変数のアドレス = スタックポインタ + 24
-# ふたつ目の変数のアドレス = スタックポインタ + 32
+# ひとつ目の変数のアドレス = フレームポインタ(fp) + 16
+# ふたつ目の変数のアドレス = フレームポインタ(fp) + 24
+# ふたつ目の変数のアドレス = フレームポインタ(fp) + 32
# ...
def var_offset(var, env)
# 変数1つにつき8バイトの領域が必要
@@ -95,29 +97,51 @@ def gen(tree, env)
# 評価した値をスタック上のローカル変数領域へ格納
gen(expr, env)
- puts "\tstr x0, sp, ##{var_offset(name, env)}"
+ puts "\tstr x0, fp, ##{var_offset(name, env)}"
elsif tree0 == "var_ref"
# スタック上のローカル変数領域からx0へ値をロード
name = tree1
- puts "\tldr x0, sp, ##{var_offset(name, env)}"
+ puts "\tldr x0, fp, ##{var_offset(name, env)}"
+ elsif tree0 == "if"
+ cexpr, texpr, fexpr = tree1, tree2, tree3
+ # 条件式を評価
+ puts "\t// 条件式を評価"
+ gen(cexpr, env)
+ puts "\tcmp x0, #0"
+ puts "\tbeq .Lelse#{tree.object_id}"
+ # 真の場合はtexprを評価
+ puts "\t// 真の場合"
+ gen(texpr, env)
+ puts "\tb .Lendif#{tree.object_id}"
+ puts ".Lelse#{tree.object_id}:"
+ # 偽の場合はfexprを評価
+ puts "\t// 偽の場合"
+ gen(fexpr, env) if fexpr
+ puts ".Lendif#{tree.object_id}:"
else
raise "invalid AST: #{tree}"
end
end
tree = minruby_parse(ARGF.read)
-env = var_names([], tree)
+env = var_names(tree)
puts "\t.text"
puts "\t.align 2"
puts "\t.globl _main"
puts "_main:"
-puts "\tsub sp, sp, ##{16 + env.size * 8}"
+lvar_size = env.size * 8
+# NOTE: スタックのサイズは16の倍数でなければならない
+puts "\tsub sp, sp, ##{16 + (lvar_size % 16 == 0 ? lvar_size : lvar_size + 8)}"
puts "\tstp fp, lr, sp, #0"
+puts "\tmov fp, sp"
gen(tree, env)
puts "\tmov w0, #0"
puts "\tldp fp, lr, sp, #0"
-puts "\tadd sp, sp, ##{16 + env.size * 8}"
+# NOTE: スタックのサイズは16の倍数でなければならない
+puts "\tadd sp, sp, ##{16 + (lvar_size % 16 == 0 ? lvar_size : lvar_size + 8)}"
puts "\tret"
diff --git a/test.sh b/test.sh
index f7cfc25..978b6d1 100755
--- a/test.sh
+++ b/test.sh
@@ -18,6 +18,12 @@ assert() {
fi
}
+# 条件分岐
+assert 42 'if (0 == 0); p(42); else p(43); end'
+assert 43 'if (0 == 1); p(42); else p(43); end'
+assert 44 'a = 44; if (a == 44); p a; end'
+assert 45 'a = 40; if (a == 40); b = 5; p a + b; end'
+
# 比較演算
# 真の場合は1、偽の場合は0を返す
assert 1 'p(1 == 1)'
while を実装
if ができれば while も簡単。
while実装のついでに、elseブロック内の変数が扱えないバグを修正したり、ローカル変数を0で初期化するようにしたりした。
code:diff
diff --git a/minrubyc.rb b/minrubyc.rb
index 4079524..370bcd9 100644
--- a/minrubyc.rb
+++ b/minrubyc.rb
@@ -7,10 +7,18 @@ def var_names(tree)
elsif tree0 == "stmts"
arr = []
tree1...each do |statement|
- arr = arr + var_names(statement)
+ arr += var_names(statement)
end
arr
elsif tree0 == "if"
+ arr = []
+ arr += var_names(tree2)
+ if tree3
+ arr += var_names(tree3)
+ end
+ arr
+ elsif tree0 == "while"
+ puts "\t// while: #{tree}"
var_names(tree2)
else
[]
@@ -103,10 +111,10 @@ def gen(tree, env)
name = tree1
puts "\tldr x0, fp, ##{var_offset(name, env)}"
elsif tree0 == "if"
- cexpr, texpr, fexpr = tree1, tree2, tree3
+ cond, texpr, fexpr = tree1, tree2, tree3
# 条件式を評価
puts "\t// 条件式を評価"
- gen(cexpr, env)
+ gen(cond, env)
puts "\tcmp x0, #0"
puts "\tbeq .Lelse#{tree.object_id}"
@@ -120,6 +128,15 @@ def gen(tree, env)
puts "\t// 偽の場合"
gen(fexpr, env) if fexpr
puts ".Lendif#{tree.object_id}:"
+ elsif tree0 == "while"
+ cond, body = tree1, tree2
+ puts ".Lwhile#{tree.object_id}:"
+ gen(cond, env)
+ puts "\tcmp x0, #0"
+ puts "\tbeq .Lendwhile#{tree.object_id}"
+ gen(body, env)
+ puts "\tb .Lwhile#{tree.object_id}"
+ puts ".Lendwhile#{tree.object_id}:"
else
raise "invalid AST: #{tree}"
end
@@ -138,6 +155,12 @@ puts "\tsub sp, sp, ##{16 + (lvar_size % 16 == 0 ? lvar_size : lvar_size + 8)}"
puts "\tstp fp, lr, sp, #0"
puts "\tmov fp, sp"
+# ローカル変数を0で初期化
+env.each do |var|
+ puts "\tmov x0, #0"
+ puts "\tstr x0, fp, ##{var_offset(var, env)}"
+end
+
gen(tree, env)
puts "\tmov w0, #0"
diff --git a/test.sh b/test.sh
index 978b6d1..5132a10 100755
--- a/test.sh
+++ b/test.sh
@@ -18,11 +18,16 @@ assert() {
fi
}
-# 条件分岐
+# while
+assert 55 'sum = 0; i = 1; while (i <= 10); sum = sum + i; i = i + 1; end; p sum'
+assert 55 'sum = 0; i = 1; while (i <= 10); one = 1; sum = sum + i; i = i + one; end; p sum'
+
+# if
assert 42 'if (0 == 0); p(42); else p(43); end'
assert 43 'if (0 == 1); p(42); else p(43); end'
assert 44 'a = 44; if (a == 44); p a; end'
assert 45 'a = 40; if (a == 40); b = 5; p a + b; end'
+assert "10\n20\n0\n" 'a = 10; if (1); b = 20; else c = 30; end; p a; p b; p c'
# 比較演算
# 真の場合は1、偽の場合は0を返す
メモ
Cでは0は偽、0以外は真として扱われる(今は bool 値もあるが、昔は無かった)
code:test.c
#include "stdio.h"
int main() {
printf("1 == 1 => %d\n", 1 == 1);
printf("1 == 0 => %d\n", 1 == 0);
printf("2 == 2 => %d\n", 2 == 2);
printf("2 == 3 => %d\n", 2 == 3);
if (1) {
printf("1 is true\n");
}
if (0) {
// do nothing
} else {
printf("0 is false\n");
}
if (2) {
printf("2 is true\n");
}
return 0;
}
code:sh
1 == 1 => 1
1 == 0 => 0
2 == 2 => 1
2 == 3 => 0
1 is true
0 is false
2 is true