コンパイラ作成(83) double型を返す関数の呼出
今回の目標
sqrtをコールしてみるよ。
// double型 extern double sqrt(double x); int main() { printf("%f\n", 2.0 * sqrt(2.0)); }
前回、関数コールに不安があったんだけど、やってみたらやっぱり駄目だったよ。見直してみたら未修正のところがあった。
関数コール
早速修正。
# 関数コールのコード生成 def codegen_func operand rettype = "int" xmm = 0 # xmmレジスタの使用数 f = @functions[operand[0].str] if @numuseregs != 0 then if @numuseregs % 2 == 1 then codegen " sub rsp, 8" end (0...@numuseregs).each do |i| codegen " push #{@regs64[i]}" end end if f != nil then if operand.size - 2 != f[1].size then perror "wrong number of parameters" end end (0...operand.size-2).each do |i| save = @numuseregs @numuseregs = i-xmm type = codegen_el operand[i+2] @numuseregs = save if f != nil then if f[1][i] == "size_t" then codegen " movsx rax, eax" type = f[1][i] elsif f[1][i] == "void*" && is_pointer_type?(type) then type = f[1][i] end if type != f[1][i] then perror "incompatible type parameter" end end if type == "int" || type == "char" then codegen " mov #{@regs32[i-xmm]}, eax" elsif type == "size_t" codegen " mov #{@regs64[i-xmm]}, rax" elsif is_pointer_type?(type) then codegen " mov #{@regs64[i-xmm]}, rax" elsif type == "double" then codegen " movsd xmm#{xmm}, xmm8" xmm += 1 else perror end end if f == nil then codegen " mov al, #{xmm}" end codegen " call " + operand[0].str if f != nil then rettype = f[0] end if rettype == "double" codegen " movsd xmm8, xmm0" end if @numuseregs != 0 then (0...@numuseregs).reverse_each do |i| codegen " pop #{@regs64[i]}" end if @numuseregs % 2 == 1 then codegen " add rsp, 8" end end return rettype end
三行追加。double型はxmm0に返ってくるんでそれをxmm8にmovsdしてる。
動作テスト
どうかな。
~/myc$ myc p11.myc /tmp/p11-6ed13e.o: 関数 `main' 内: (.text+0x38): `sqrt' に対する定義されていない参照です clang: error: linker command failed with exit code 1 (use -v to see invocation) ~/myc$
ありゃ。ああ、そっか。-lm付けなきゃいけないのか。
~/myc$ myc -c p11.myc ~/myc$ clang-5.0 p11.s -o p11 -lm ~/myc$ ./p11 2.828427
お、できた。もういっちょ行ってみるよ。
// double型の乱数 extern double sqrt(double x); extern size_t time(size_t *t); extern void srand48(size_t seed); extern double drand48(); int main() { size_t t; srand48(time(&t)); double x = drand48(); for(int i = 0; i < 10; i = i + 1) printf("%f\n", drand48()); }
timeのextern宣言おかしいのはまだlong型をサポートしてないから。それに型キャストもできないからね。無理やりだけどこれでいけるかな。
~/myc$ myc -c p14.myc ~/myc$ clang-5.0 p14.s -o p14 -lm ~/myc$ ./p14 0.517519 0.828659 0.209253 0.540878 0.017379 0.805752 0.770110 0.495048 0.851670 0.865793 ~/myc$
おお、上手く行ったよ。しかし、-lm指定するのにclangを呼ばないと駄目なのは面倒くさいなあ。。mycから-lmを渡してやればいいんだよなあ。簡単に修正できるかな。ちょっとやってみる。
メイン部
# コマンドラインオプションの解析 $opt_c = false $opt_d = false $opt_l = [] $opt_L = false $opt_O = 3 $opt_v = false $opt_cc = "clang-5.0" OptionParser.new do |opt| opt.on('-c', 'Compile only') {|v| $opt_c = v} opt.on('-d', 'Debug mode') {|v| $opt_d = v} opt.on('-l module','Link module') {|v| $opt_l << ("-l"+v)} opt.on('-L', 'Lexer module test') {|v| $opt_L = v} opt.on('-O val', 'Optimize level') {|v| $opt_O = v.to_i} opt.on('-v', 'Version') {|v| $opt_v = v} opt.on('--cc val', 'Assemble & Link') {|v| $opt_cc = v} begin opt.parse!(ARGV) rescue puts "Invalid option. \nsee #{opt}" exit end end # versionの表示 if $opt_v then puts "myc version #{$ver}" exit 0 end # 字句解析単体テスト if $opt_L then fname = ARGV[0] lex = Lexer.new(fname) lex.moduletest lex.close exit 0 end # コンパイル f = [] execfname = nil ARGV.each do |fname| if fname.match(/\.myc$/) then asmfname = fname.sub(/\.myc$/,'.s') # アセンブリコードのファイル名 if !execfname then execfname = fname.sub(/\.myc$/,'') end parser = Parser.new(fname) parser.program f << asmfname else f << fname end end if !$opt_c then system("#{$opt_cc} #{f.join(' ')} -o #{execfname} #{$opt_l.join(' ')}") end exit 0
Lexerクラスの単体テストのオプションとバッティングしちゃったんで、そっちは-Lに変更したよ。
動作テスト再び
~/myc$ myc p11.myc -lm ~/myc$ ./p11 2.828427
できたよ。一応-lは複数付けられるようにしたつもり。その内テストしとこう。(多分忘れる)
しかし、double型はやっぱり難しいなあ。char型と比べてやらなきゃならないことが多いよ。まあ大変な分面白いけどね。知らなかったこと知れるしね。次回はdouble型の比較演算かな。