TinyRubyコンパイラ/関数を呼び出す
#TinyRuby
from TinyRubyコンパイラ
引数が1つだけのプリント関数 p はすでに呼び出せているので、次は通常の関数を呼び出せるようにする。
引数なしの関数呼び出し
まずは、引数なしの関数 hello を呼び出せるようにする。
libtinyruby.c へ hello 関数を追加し、
code:diff
diff --git a/libtinyruby.c b/libtinyruby.c
index f785f3b..a0ac988 100644
--- a/libtinyruby.c
+++ b/libtinyruby.c
@@ -5,3 +5,8 @@ long p(long n) {
printf("%ld\n", n);
return n;
}
+
+void hello() {
+ // hello
+ printf("86\n");
+}
引数が無い場合の構文木を確認。
code:ruby
irb(main):004:0> minruby_parse "hello()"
=> "func_call", "hello"
引数が0個の場合の関数呼び出しを雑に実装する。
code:ruby
...
when "func_call"
# 引数を評価
args = node2..
if args.size == 0
# 何もしない
elsif args.size == 1
# 引数を評価して rdi レジスタにセット
gen(node2, env)
puts " mov rdi, rax"
end
# 関数を呼び出す
puts " call #{node1}"
when "stmts"
...
実行してみる。ハロー(86)が出力されてるので大丈夫そう。
code:sh
$ echo "hello()" | ruby tinyrubyc.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c && ./a.out
86
1つ以上の引数の関数呼び出し
次は、引数が1つ以上の関数を呼び出す。
libtinyruby.c へ sum6 関数を追加。
code:diff
diff --git a/libtinyruby.c b/libtinyruby.c
index f785f3b..b681293 100644
--- a/libtinyruby.c
+++ b/libtinyruby.c
@@ -5,3 +5,12 @@ long p(long n) {
printf("%ld\n", n);
return n;
}
void hello() {
// hello
printf("86\n");
}
+long sum6(long a1, long a2, long a3, long a4, long a5, long a6) {
+ return a1 + a2 + a3 + a4 + a5 + a6;
+}
関数呼び出す際にどのようにして引数を渡すか、どのようにして戻り値を返すか、レジスタの値の退避が必要かどうか、などの規約は CPU/OS ごとに定められており(Application Binary Interface = ABIと呼ばれる)、今回ターゲットとしている x86_64/Linux のABIは以下の資料などで確認することができる。
System V Application Binary Interface AMD64 Architecture Processor Supplement
https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
x86_64/Linux での引数は、1つ目から6つ目までの引数はレジスタへ、7つ目以降の引数はスタックへ格納することになっている。今回は実装を単純にするために7つ目以降の引数はサポートしないこととする。1つ目から6つ目の引数は以下のレジスタに格納する(参照:x86-64のレジスタ )。
1つ目の引数 → rdi レジスタ
2つ目の引数 → rsi レジスタ
3つ目の引数 → rdx レジスタ
4つ目の引数 → rcx レジスタ
5つ目の引数 → r8 レジスタ
6つ目の引数 → r9 レジスタ
1つ以上の引数関数呼び出しの実装は以下のような感じ。
code:ruby
...
when "func_call"
args = node2..
# 引数が6個以上の場合はエラー
raise "too many arguments (given #{args.size}, expected 6)" if args.size > 6
# 関数の引数を評価してスタックへ積む
args.each do |arg|
gen(arg, env)
puts " push rax"
end
# スタックへ積んだ引数を、引数渡し用のレジスタへセット
args.each_with_index.reverse_each do |_, i|
puts " pop rax"
puts " mov #{ARG_REGISTERSi}, rax"
end
# 関数を呼び出す
puts " call #{node1}"
...
実行してみる。1から6までの和の21が返ればOK。
code:sh
$ echo "p sum6(1, 2, 3, 4, 5, 6)" | ruby tinyrubyc.rb > tmp.s
$ gcc -z noexecstack tmp.s libtinyruby.c && ./a.out
21