コンパイラ作成(86) double型の単項マイナス演算子

今回の目標

今回は単項マイナス演算子だよ。

// double型
int main()
{
    double x = 4.5, y = -x;
    printf("x = %f\ny = %f\n", x, y);
}
コード生成部

codegen_unaryopの該当部分を修正。

  # 単行演算子のコード生成
  def codegen_unaryop(op, operand)
    type = "int"
    if op.str == "-" then
      # 単行マイナス演算子
      type = codegen_el operand
      if type == "char" then
        codegen "  neg  al"
      elsif type == "int" then
        codegen "  neg  eax"
      elsif type == "double" then
        codegen "  movq   rax, xmm8"
        codegen "  movabs r10, 8000000000000000H"
        codegen "  xor    rax, r10"
        codegen "  movq   xmm8, rax"
      else
        perror "unsupported type with unary '-'"
      end

いつものようにclang先輩のコードを参考にしたんだけど分かり辛いよねこれ。ようはサインビットを反転してるんだけど、なんでこんな方法になってるのかな。これが一番早いのかな。それとも何か別の理由があるんだろうか。単純に0から引くのじゃ拙いのかな。うーん。

動作テスト
~/myc$ myc p22.myc
~/myc$ ./p22
x = 4.500000
y = -4.500000
~/myc$

できたよん。これでdouble型の演算は全部できるようになったかな。次回は引数がdouble型の関数定義かな。関数コールはもう終わってるけど定義の方はまだ未対応だよ。

コンパイラ作成(85) double型の比較演算子

今回の目標

前回の残りの比較演算をサポートするよ。

// double型の比較
int main()
{
    double x;
    x = 1.5;
    if( x != 1.5 )
        printf("true\n");
    else
        printf("false\n");
    x = 1.0;
    if( x != 1.5 )
        printf("true\n");
    else
        printf("false\n");
}
コード生成部

修正箇所は前回と同じ。

  # ニーモニック
  def mnemonic(op, type)
    if type == "double" then
      if op.str == "+" then
        return "addsd "
      elsif op.str == "-" then
        return "subsd "
      elsif op.str == "*" then
        return "mulsd "
      elsif op.str == "/" then
        return "divsd "
      elsif op.str == "%" then
        return "divsd "
      elsif op.str == "==" then
        return "ucomisd"
      elsif op.str == "!=" then
        return "ucomisd"
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "ucomisd"
      else
        perror "unknown operator \"" + op.str + "\""
      end
    else
      if op.str == "+" then
        return "add "
      elsif op.str == "-" then
        return "sub "
      elsif op.str == "*" then
        return "imul"
      elsif op.str == "/" then
        return "idiv"
      elsif op.str == "%" then
        return "idiv"
      elsif op.str == "==" then
        return "cmp "
      elsif op.str == "!=" then
        return "cmp "
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "cmp "
      else
        perror "unknown operator \"" + op.str + "\""
      end
    end
  end

codegen_elsの比較演算子の処理部。

    # 左被演算子と右被演算子とで計算
    if op.str == "==" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  sete  al"
        codegen "  setnp r10b"
        codegen "  and   al, r10b"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  sete al"
        codegen "  and  eax, 1"
      end
    elsif op.str == "!=" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setne al"
        codegen "  setp  r10b"
        codegen "  or    al, r10b"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setne al"
        codegen "  and  eax, 1"
      end
    elsif op.str == "<" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setb  al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setl al"
        codegen "  and  eax, 1"
      end
    elsif op.str == ">" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  seta  al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setg al"
        codegen "  and  eax, 1"
      end
    elsif op.str == "<=" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setbe al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setle al"
        codegen "  and  eax, 1"
      end
    elsif op.str == ">=" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setae al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setge al"
        codegen "  and  eax, 1"
      end

前回同様、clang先輩の出力結果を参考に。

動作テスト
~/myc$ myc p16.myc
~/myc$ ./p16
false
true
~/myc$

大丈夫だね。残りの演算子についても同様にチェック。地味で面倒な作業だけどサボらずやったよ。これだけじゃ面白くないんでもういっちょやってみるよ。

// モンテカルロ法による円周率の計算
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 count = 0.0;
    for(int i = 0; i < 1000000; i = i + 1) {
        double x = drand48();
        double y = drand48();
        if( x*x + y*y <= 1.0 ) count = count + 1.0;
    }
    printf("pi = %10.8f\n",count / 1000000.0 * 4.0);
}

どうかな。

~/myc$ myc p21.myc
~/myc$ ./p21
pi = 3.14105600
~/myc$

できたできた。countがdouble型になってるのは型変換がまだできないから。これも宿題だなあ。あとtime(0)とかtime(NULL)とか書けるようにしたいなあ。さて次は単項演算子かな。

コンパイラ作成(84) double型の等価演算子

今回の目標

等価演算子をサポートするよ。

// double型の乱数
int main()
{
    double x;
    x = 1.5;
    if( x == 1.5 )
        printf("true\n");
    else
        printf("false\n");
    x = 1.0;
    if( x == 1.5 )
        printf("true\n");
    else
        printf("false\n");
}
コード生成部

まずはここを修正。

  # ニーモニック
  def mnemonic(op, type)
    if type == "double" then
      if op.str == "+" then
        return "addsd "
      elsif op.str == "-" then
        return "subsd "
      elsif op.str == "*" then
        return "mulsd "
      elsif op.str == "/" then
        return "divsd "
      elsif op.str == "%" then
        return "divsd "
      elsif op.str == "==" then
        return "ucomisd"
      elsif op.str == "!=" then
        return "cmp "
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "cmp "
      else
        perror "unknown operator \"" + op.str + "\""
      end
    else
      if op.str == "+" then
        return "add "
      elsif op.str == "-" then
        return "sub "
      elsif op.str == "*" then
        return "imul"
      elsif op.str == "/" then
        return "idiv"
      elsif op.str == "%" then
        return "idiv"
      elsif op.str == "==" then
        return "cmp "
      elsif op.str == "!=" then
        return "cmp "
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "cmp "
      else
        perror "unknown operator \"" + op.str + "\""
      end
    end
  end

ucomisdってので比較できるみたいだ。次はcodegen_elsの等価演算子の処理のところ。

    # 左被演算子と右被演算子とで計算
    if op.str == "==" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  sete  al"
        codegen "  setnp r10b"
        codegen "  and   al, r10b"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  sete al"
        codegen "  and  eax, 1"
      end

clang先輩の出力を参考にしたんだけど、なぜこうなってるのか理解できないよ。この辺の知識が不足してるなあ。

動作テスト
~/myc$ ./p15
true
false
~/myc$

ちゃんと動いてるね。今回はあんまり時間取れなかったんでちゃんと理解することなく、clang先輩の真似っこしちゃったよ。時間ができたらもう一度見直さないと駄目だな。今回は等価演算子一個しか追加できなかったけど、次回は頑張って残りの比較演算子全部追加したいなあ。

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

コンパイラ作成(82) double型の複雑な四則演算

今回の目標

前回に続き四則演算。

// double型

int main()
{
    printf("%f\n", 2.0 + 5.0 / 2.0);
}

前回対応できなかった複雑な式。

コード生成部

codegen_elsの括弧の処理部と関数呼出の処理部を修正。

  def codegen_els(op, operand, type_l)
    ostr = mnemonic op, type_l

    # 右被演算子を評価
    type_r = "int"
    if operand.kind_of?(Array) then
      if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then
        if type_l == "double" then
          codegen "  sub    rsp, 16"
          codegen "  movsd  [rsp], xmm8"
          type_r = codegen_func operand
          codegen "  movsd  xmm9, xmm8"
          codegen "  movsd  xmm8, [rsp]"
          codegen "  add    rsp, 16"
          str = "xmm9"
        else
          codegen "  sub  rsp, 8"
          codegen "  push rax"
          type_r = codegen_func operand
          codegen "  mov  r10d, eax"
          codegen "  pop  rax"
          codegen "  add  rsp, 8"
          str = "r10d"
        end
      else
        if type_l == "double" then
          codegen "  sub    rsp, 16"
          codegen "  movsd  [rsp], xmm8"
          type_r = codegen_el operand
          codegen "  movsd  xmm9, xmm8"
          codegen "  movsd  xmm8, [rsp]"
          codegen "  add    rsp, 16"
          str = "xmm9"
        else
          codegen "  sub  rsp, 8"
          codegen "  push rax"
          type_r = codegen_el operand
          codegen "  mov  r10d, eax"
          codegen "  pop  rax"
          codegen "  add  rsp, 8"
          str = "r10d"
        end
      end

レジスタの退避処理だけど、xmmレジスタはpush/popできないんだね。仕方ないんでrsp弄った後にmovsdするようにしたよ。

動作テスト
~/myc$ myc p10.myc
~/myc$ ./p10
4.500000
~/myc$

お、ちゃんと計算できるようになったよ。今回ので式の途中での関数呼出もできるようになったはずだよなあ。ああ、でももう一回処理を見直した方が良いかな。現状でどこまで実装できたのか分からなくなってきたよ。拙い状況だよ。うーむ。

Haskellのお勉強(4) 素数

素数の表示

Haskellだけど細々と勉強続けてるよ。でもなかなか進まない。解説サイト見たりしてるんだけどさ、書かれてる内容は理解できても、それを実際のプログラミングの中でどう活かせば良いのかが分からないんだよね。ということでまた簡単なとこから。

import System.Environment   
import Text.Printf

prime p [] = p
prime p (x:xs) =
   prime (p ++ [x]) [ i | i <- xs, i `mod` x /= 0 ]

main = do
    args <- getArgs
    let n = if args == [] then 100 else read(args !! 0)
    print $prime [] [2..n]

エラトステネスの篩。再帰使ってやってみたよ。

~/haskell$ runghc prime.hs
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
~/haskell$

できたよ。相変わらずちょっとした記述間違いでドツボにハマっちゃうよ。prime p (x:xs) =のところをprime p x:xs =って間違えてたんだけど、それが原因だって気付くまでえらく時間掛かっちゃった。
計算途中でリストのお尻に要素を追加してるんだけど、こういうのHaskellは苦手なのかな。Lispと同じ感じで。ってことでちょっと変更してみる。

import System.Environment   
import Text.Printf

prime p [] = reverse p
prime p (x:xs) =
   prime (x:p) [ i | i <- xs, i `mod` x /= 0 ]

main = do
    args <- getArgs
    let n = if args == [] then 100 else read(args !! 0)
    print $prime [] [2..n]

リストの先頭に要素を追加していくようにして、最後にreverseするようにしてみたよ。

~/haskell$ runghc prime2.hs
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
~/haskell$

同じ結果が得られたよ。おそらくprime2.hsの方が速いんだと思うけどどうだろうか。計測してみる。

~/haskell$ time runghc prime.hs 10000
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,
途中省略
,9871,9883,9887,9901,9907,9923,9929,9931,9941,9949,9967,9973]

real	0m3.346s
user	0m2.567s
sys	0m0.178s
~/haskell$ time runghc prime2.hs 10000
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,
途中省略
,9871,9883,9887,9901,9907,9923,9929,9931,9941,9949,9967,9973]

real	0m2.715s
user	0m2.300s
sys	0m0.161s
~/haskell$

おお、やっぱりprime2.hsの方がちょっと速いね。

vlangを試してみた

vlang

巷で話題になってるvlangを触ってみたよ。
vlang.io
奇麗なページでしっかりしたプログラミング言語なのかと思ったけど、一人だけで作ってるんだね。正直出来はまだまだって感じみたいだ。コンパイラの中身をちょこっと見てみたけど、C言語へのトランスレータだね。言語自体はGoに近いのかな。俺はGoは触ったことないんで良く分からないんだけど。変数がデフォルトでイミュータブルなのとかはRustっぽいのかな。

試してみる
#include <stdio.h>

fn main() {
  for i := 1; i <= 9; i++ {
    for j := 1; j <= 9; j++ {
      C.printf('%3d', i*j)
    }
    println('')
  }
}
~/vlang$ v run test2.v
============running test2==============================
  1  2  3  4  5  6  7  8  9
  2  4  6  8 10 12 14 16 18
  3  6  9 12 15 18 21 24 27
  4  8 12 16 20 24 28 32 36
  5 10 15 20 25 30 35 40 45
  6 12 18 24 30 36 42 48 54
  7 14 21 28 35 42 49 56 63
  8 16 24 32 40 48 56 64 72
  9 18 27 36 45 54 63 72 81
~/vlang$

書式付き出力の方法が良く分からなかったけど、C言語のprintfが呼べるのか。printfに限らず困ったらC言語のルーチン呼び出せば良いのかな。

雑感

今のところ将来性がある言語なのか分からないな。Github見てると結構な勢いでコミットされてるね。もしかしたらバケるのかもしれないけど、作者が飽きてほっぽり出しちゃう可能性もあるな。とりあえず注目を集めることだけは成功してる。これは間違いないんだけどね。さてさて。