TinyRubyコンパイラ/比較演算を実装する
次は、TinyRubyコンパイラへ以下の比較演算子を導入する。
==
!=
>
>=
<
<=
TinyRubyには整数型しかサポートしないため、真の場合は 1 を、偽の場合は 0 を返す。
x86-64での比較演算の実装
x86-64では以下の手順で比較演算を行う。
cmp 命令で2つのレジスタを比較
sete命令などを用いて比較結果を8ビット汎用レジスタ格納
movzx 命令で、8ビット汎用レジスタに格納された値を64ビットレジスタへ移動
各命令の概要は以下のとおり。
cmp 命令
2つのレジスタの大小比較を行い、結果をフラグレジスタへ格納する
sete (set equal) 命令
cmp 命令の結果、2つのレジスタの値が同値だった場合は 1 を、同値でなかった場合は 0 を否かを指定したレジスタへセットする
sete の結果は8ビットレジスタにしか格納できないため、後で movzx 命令を使って64ビットレジスタへ詰め替える必要がある
setne (set not equal) 命令
cmp 命令の結果、2つのレジスタの値が同値でない場合は 1 を、同値だった場合は 0 を否かを指定したレジスタへセットする
setg (set greater) 命令
左辺の方が大きければ 1 を、そうでない場合は 0 を、指定したレジスタへセットする
setge (set greater or equal) 命令
左辺の方が大きい、または同値であれば 1 を、そうでない場合は 0 を、指定したレジスタへセットする
setl (set less) 命令
左辺の方が小さければ 1 を、そうでない場合は 0 を、指定したレジスタへセットする
setle (set less or equal) 命令
左辺の方が小さい、または同値であれば 1 を、そうでない場合は 0 を、指定したレジスタへセットする
movzx 命令
8ビットレジスタへ格納した比較演算結果を64ビットレジスタへ移す
サンプルプログラムはこんな感じ。
code:asm
.intel_syntax noprefix
.globl main
main:
push rbp
mov rbp, rsp
sub rsp, 0
push r12
push r13
;; r12 = 1
mov rax, 1
mov r12, rax
;; r13 = 2
mov rax, 2
mov r13, rax
;; rax = r12 < r13
cmp r12, r13
setl al
movzx rax, al
pop r13
pop r12
;; 1 < 2 の比較結果を出力
mov rdi, rax
call p
add rsp, 0
pop rbp
ret
比較演算を実装
TinyRubyへ比較演算を追加してみる。
code:diff
diff --git a/test.sh b/test.sh
index e2cc846..9a99306 100755
--- a/test.sh
+++ b/test.sh
@@ -38,21 +38,22 @@ assert() {
# assert 41 'if (0 == 0); p(41); end'
# assert '' 'if (0 == 1); p(41); end'
-# # 真の場合は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)'
+# 真の場合は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 1 'p(99999999999999999 == 99999999999999999)'
# # 変数
assert 30 'a = 10; b = 20; p a + b'
diff --git a/tinyrubyc.rb b/tinyrubyc.rb
index 5ae548a..649dd71 100644
--- a/tinyrubyc.rb
+++ b/tinyrubyc.rb
@@ -79,6 +79,52 @@ def gen(node, env)
puts " idiv r13"
end
+ # r12 と r13 の値をスタックから復元
+ puts " pop r13"
+ puts " pop r12"
+ when "==", "!=", ">", ">=", "<", "<="
+ # 比較演算の場合
+
+ # r12 と r13 の値をスタックへ退避
+ puts " push r12"
+ puts " push r13"
+
+ # 左辺の計算結果を r12 へ格納
+ puts " mov r12, rax"
+
+ # 右辺の計算結果を r13 へ格納
+ puts " mov r13, rax"
+
+ when "=="
+ puts " cmp r12, r13"
+ puts " sete al"
+ puts " movzx rax, al"
+ when "!="
+ puts " cmp r12, r13"
+ puts " setne al"
+ puts " movzx rax, al"
+ when ">"
+ puts " cmp r12, r13"
+ puts " setg al"
+ puts " movzx rax, al"
+ when ">="
+ puts " cmp r12, r13"
+ puts " setge al"
+ puts " movzx rax, al"
+ when "<"
+ puts " cmp r12, r13"
+ puts " setl al"
+ puts " movzx rax, al"
+ when "<="
+ puts " cmp r12, r13"
+ puts " setle al"
+ puts " movzx rax, al"
+ end
+
# r12 と r13 の値をスタックから復元
puts " pop r13"
puts " pop r12"
実行してみる。「a == b」の結果に0(false)が、「a == a」の結果に1(true)が返ればOK
code:sh
$ echo "a = 10; b = 20; p(a == b); p(a == a)" | ruby tinyrubyc.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c && ./a.out
0
1