TinyRubyコンパイラ/組込み関数の呼び出し
次は、プリント関数を使って計算結果を画面に出力できるようにする。
code:ruby
# 計算結果の 30 が画面に出力される
p(10+20)"
構文木を確認
まずは構文木を確認する。
code:irb
irb(main):001:0> minruby_parse "p(10+20)"
構文木には以下の要素が格納されている。
配列の0番目の要素(ノードの種類)
func_call
配列の1番目位の要素(呼び出す関数の名前)
p
配列の2番目の要素(関数に渡す引数)
["+", ["lit", 10], ["lit", 20]]]
関数の呼び出し方を確認
関数の呼び出し方を確認するため、以下のC言語のソースコードからアセンブリを出力する。
code:test.c
long p(long l) {
printf("%ld\n", l);
}
int main() {
p(123);
return 0;
}
code:sh
$ gcc -S -masm=intel -fno-asynchronous-unwind-tables test.c
第一引数を edi レジスタに渡して、call 命令で p 関数を呼び出している。
code:test.s
... (省略) ...
main:
push rbp
mov rbp, rsp
; 第一引数の値を edi レジスタへセット
mov edi, 123
; 関数 p の呼び出し
call p
mov eax, 0
pop rbp
ret
... (省略) ...
プリント関数の呼び出しの実装
プリント関数の呼び出しを実装する。
code:diff
diff --git a/tinyrubyc.rb b/tinyrubyc.rb
index b756a43..39f3120 100644
--- a/tinyrubyc.rb
+++ b/tinyrubyc.rb
@@ -42,6 +42,13 @@ def gen(node)
# r12 と r13 の値をスタックから復元
puts " pop r13"
puts " pop r12"
+ elsif node0 == "func_call" && node1 == "p" + # 引数を評価して、rdi レジスタにセット
+ puts "mov rdi, rax"
+
+ # p 関数を呼び出す
end
end
p(10+20) をコンパイルして出力されるアセンブリはこんな感じ。
code:tmp.s
.intel_syntax noprefix
.globl main
main:
; 関数プレリュード
push rbp
mov rbp, rsp
; r12 と r13 の値を退避
push r12
push r13
; r12 = 10
mov rax, 10
mov r12, rax
; r13 = 20
mov rax, 20
mov r13, rax
; rax = r12 + r13
add r12, r13
mov rax, r12
; r12 と r13 の値を元に戻す
pop r13
pop r12
; p 関数の呼び出し
mov rdi, rax
call p
; 関数エピローグ
pop rbp
ret
プリント関数を用意する
プリント関数 p は、TinyRuby の組み込み関数として libtinyruby.c で実装する。
code:libtinyruby.c
// プリント関数
long p(long n) {
printf("%ld\n", n);
return n;
}
libtinyruby.c は、TinyRuby コンパイラが出力したアセンブリと一緒にビルドする。ビルド後に実行して、実行結果の 30 が画面に出力されればOK
code:sh
$ gcc -z noexecstack tmp.s libtinyruby.c
$ ./a.out
30
他のプログラムも実行してみる。ちゃんと動いていそう。
code:sh
$ echo "p(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)" | ruby tinyrubyc.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c
$ ./a.out
55