JavaScript
Node.js
LLVM

Node.js でつくる Node.js ミニコンパイラ - 01 : 仕様の検討と、LLVMの準備

はじめに

「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」(ラムダノート, Amazon) という本を手を読んで非常に感銘を受けました。そこでも自分でもNode.jsでミニNode.jsインタープリタを作ってみました。なんとか達成して大きな自己満足を味わうことができました。

実はそのチャレンジにはもう一つのきっかけがあります。それは Turing Complete FM のポッドキャストを聴き始めたのことです。
このポッドキャストを聴きなら、そういえば随分昔に「自分で小さなコンパイラを作ること」に憧れていたことを思い出しました。小さなインタープリタを作れた今なら当時挫折したことができそうな気がして、今度はコンパイラ作りを目指します。

目指すもの

ミニインタープリタを作って分かったことは、適切に小さくした目標が重要だということです。今回目指すミニNode.jsコンパイラも、思い切って割り切った言語仕様にすることにしました。

  • FizzBuzzと、フィボナッチ数列の計算ができるようする
  • 仕様はミニNode.js (つまり、ミニRuby)を踏襲する
    • ただし、配列やハッシュは除外する
  • 型は32ビット符号付き整数のみサポート
  • ローカル変数が使える。グーローバル変数は無し
  • if - else, while が使える
  • ユーザ定義関数が使える(再帰呼び出しもできるようにしたい)
  • 組み込み関数として、整数の出力と固定文字列の出力ができるようにする

コンパイラなので型を導入しますが、1種類に割り切るというJavaScriptらしからぬ仕様にしました。これで型宣言も型推論も不要です(ドヤ顔)。

またコンパイラなので、最終的にはNode.js無しで実行できるバイナリを生成しなければなりません。が、そこは便利なツールを利用させてもらいます。

  • 単独で実行できるバイナリを生成することを目指す
    • バイナリ生成は、Node.jsではなくて別のツールを利用する
    • ミニコンパイラでは、中間コードを生成する
  • 自分自身をコンパイルすることは諦める(セフルホストはしない)
    • ミニコンパイラ自体は、ミニNode.jsインタープリタで実行できることを目指す

コード生成に使うツール

最終的にバイナリを生成するところは、他のツールに任せることにしました。これでかなりコンパイラを作るハードルが下がります。ツールの候補としては、次のものを考えました。

  • Cコンパイラ (gcc or Clang)
  • Goコンパイラ
  • Rustコンパイラ
  • LLVM

最初の3つの場合は、Node.jsから各言語のソースコードに変換することになります。コンパイラを作ったというと、ちょっと違う感じがします。
そこで以前から気になっていたLLVMを使うことにしました。つまり、 Node.jsでLLVMの中間表現(Internal Representation)を生成することになります。

LLVMの準備

LLVMとは

LLVMによると

LLVMプロジェクトは、モジュール化された再利用可能なコンパイラおよびツールチェーン技術の集まりです

とあります。もともとは Low Level Virtual Machine の略語として名付けられたらしいのですが、いまはLLVMが正式な名称とのことです。
最近の言語系ではよく利用さているらしく、例えばClang, Swift, Rustが代表例です。ASM.jsやWebAssemblyを生成するEmscriptenもLLVMを利用しているそうです。
ちなみにGoもLLVMを使っているのかと思っていたのですが、実際は違いました。コンパイラもライブラリも自前で作るのがGoの流儀なようです。

LLVMのインストール

LLVM は Clang と一緒に提供されているようです。(Download page) その中で、今回は次のツールを使います。

  • lli ... LLVM IR(中間表現)やBitcode(バイトコード)を実行するツール
  • llc ... 実行モジュールのバイナリを生成するツール

Mac OS X の場合、clangは /usr/bin/clang にインストールされていますが、このツール群は含まれていません。また Xcode をインストールした場合にも clang がインストールされますが、lli/llcといったツールは無いようです。

Clang/LLVMのインストールには次の方法があります。

  • (1) ソースコードからビルドする
  • (2) homebrewを使ってインストールする(Mac OS Xの場合)
  • (3) ビルド済みのバイナリ(Pre-build)をダウンロードする

今回はなるべく簡単にすませたくて、3番目のビルド済みバイナリをダウンロードする方法をとりました。Mac OS Xで作業しているため、こちらのページから Clang for Mac をダウンロードします。2018年7月現在、v6.0.0のバイナリが最新のようです。

圧縮ファイルを解凍すると中に下記のフォルダーが現れます。

  • bin
  • share
  • include
  • lib
  • libexec

一式をお好きな場所にコピーしてbinにパスを通してください。私の場合はパスを通す代わりに次のようにしました。

  • ~/Application/clang/ 以下に各フォルダーをコピー
  • bashのエイリアスを設定
    • alias llc='~/Applications/clang/bin/llc'
    • alias lli='~/Applications/clang/bin/lli'

次回は

つぎはLLVM IRの書き方を調べてみます。こちらのページを参考にします。