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)
- elsif %w(+ - * /).include?(tree0) + elsif %w(+ - * / == != < <= > >=).include?(tree0) @@ -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)
- arr.include?(tree1) ? arr : arr + [tree1] - 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
+ 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)
# スタック上のローカル変数領域からx0へ値をロード
+ cexpr, texpr, fexpr = tree1, tree2, tree3 + # 条件式を評価
+ puts "\t// 条件式を評価"
+ gen(cexpr, env)
+ 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
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 "\tmov fp, sp"
gen(tree, env)
-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)
arr = []
tree1...each do |statement| - arr = arr + var_names(statement)
+ arr += var_names(statement)
end
arr
+ arr = []
+ arr += var_names(tree2) + arr += var_names(tree3) + end
+ arr
else
[]
@@ -103,10 +111,10 @@ def gen(tree, env)
- cexpr, texpr, fexpr = tree1, tree2, tree3 + cond, texpr, fexpr = tree1, tree2, tree3 # 条件式を評価
puts "\t// 条件式を評価"
- gen(cexpr, env)
+ gen(cond, env)
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}:"
+ cond, body = tree1, tree2 + puts ".Lwhile#{tree.object_id}:"
+ gen(cond, env)
+ puts "\tbeq .Lendwhile#{tree.object_id}"
+ gen(body, env)
+ puts "\tb .Lwhile#{tree.object_id}"
+ puts ".Lendwhile#{tree.object_id}:"
else
end
@@ -138,6 +155,12 @@ puts "\tsub sp, sp, ##{16 + (lvar_size % 16 == 0 ? lvar_size : lvar_size + 8)}" puts "\tmov fp, sp"
+# ローカル変数を0で初期化
+env.each do |var|
+end
+
gen(tree, env)
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
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