コンパイラ作成(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型の比較演算かな。