自作言語をWebAssemblyに対応する試み

## 自作言語のすすめ

ある程度プログラミングに慣れてくると、「自分で設計したオリジナル言語」を作りたくなりますよね？...ね？

自分で設計した言語を使って、ウェブのフロントエンドアプリ開発に利用できたら楽しそうです。実用性は無いに等しく、ましてやお仕事での開発で利用できるはずもありませんが、技術的な興味と自己満足で、自作言語でWebAssemblyのアプリを作ってみようという試みです。

## orelang

皆さんはorelangについて覚えているでしょうか？
覚えてるも何も、知らねぇよそんなもん初めて聞いた、というかもしれません。元ネタは3年前に一部界隈で流行った、とても低機能な自作言語の記事です。

▼プログラミング言語を作る。１時間で。
https://qiita.com/shuetsu@github/items/ac21e597265d6bb906dc

制御構造は条件つきループの`until`のみ。演算子は比較の`=`と加算の`+`のみという、とてもシンプルな設計で、JSONで記述します。

サンプルプログラムは次のようなものです。なんとなく雰囲気は掴めるでしょうか。

``` .json
["step",
  ["set", "sum", 0 ],
  ["set", "i", 1 ],
  ["while", ["<=", ["get", "i"], 10],
	["step",
	  ["set", "sum", ["+", ["get", "sum"], ["get", "i"]]],
	  ["set", "i", ["+", ["get", "i"], 1]]]],
  ["print", ["get", "sum"]]]
```

当時作成したときの都合により、`until`の代わりに`while`、演算子は`=`ではなく`<=`で実装していますが、大筋では変わりません。最初に`sum`に`0`、`i`に`1`を代入し、`i`が`10`になるまでインクリメントしながらループします。ループがまわる度に`sum`に`i`を足していきます。ループを抜けたとき`sum`の値が1から10の総和である55になっていれば正解です。最後の`print`関数は、整数値を1個表示することができます（それしかできません）

## LLVM

LLVMはC系言語で利用されているコンパイラ基盤です。clangコンパイラの最も重要な部分で使われているのでお馴染みです。

CやC++など、clangコンパイラが対応している言語であればWebAssemblyのバイトコードが生成できるのなら、LLVMのAPIで生成した LLVM IR からのWebAssembly化もできるはずだよね。だったら、自作言語のWebAssembly対応も可能なのでは、というのがこの記事の趣旨です。

## clangをインストールする

Ubuntu 18上での開発を想定しています。clang-6.0を使用します。

`sudo apt install clang`

忘れずにバージョンを確認しておきます。

```
soramimi@ubuntu18x64:~$ clang -v
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
...
```

InstalledDirの行に注意してください。上記の例ではシステムにインストールされたUbuntu標準のclangが実行されました。

もし、WebAssembly（Emscripten SDK）の開発環境だと次のようになると思います。


```
soramimi@ubuntu18x64:~$ source ~/emsdk/emsdk_env.sh
...
soramimi@ubuntu18x64:~$ clang -v
clang version 6.0.1  (emscripten 1.38.30 : 1.38.30)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/soramimi/emsdk/clang/e1.38.30_64bit
...
```

ローカル開発環境とWebAssemby開発環境を行ったり来たりしていると、時々どちらのclangが実行されているか混乱することがありますので注意が必要です。異変に気づいたらどちらのclangが使われているか確認しましょう。

`which clang`

ローカル開発環境でclangを使いたいときは、一度ターミナルを閉じて開き直します。

## やってみる

お馴染みのプログラムで試してみます。

``` hello.c
#include <stdio.h>
int main()
{
    printf("Hello, world\n");
    return 0;
}
```

普通に実行するなら次の通りです。

```
$ clang hello.c
$ ./a.out
Hello, world
$
```

次に LLVM IR を経由してコンパイルしてみます。

```
$ clang -c -S -emit-llvm hello.c -o hello.ll
```

`.ll`という拡張子のファイルがLLVM IRです。このファイルの中を覗いてみるとアセンブリ言語っぽいですが、簡単に言うと、コンパイル中の抽象構文木を元に生成された、特定のCPUに依存しない仮想のアセンブリ言語と思っておけば良いです。

これを実行ファイルにするのはC/C++などと同じ要領でできます。

```
$ clang hello.ll
$ ./a.out
Hello, world
$
```

## LLVM IRをWebAssembly化する

ここからはWebAssembly開発環境を利用しますので、Emscripten SDKのための環境変数を設定します。環境構築がまだの方は**[先日の記事](https://qiita.com/soramimi_jp/items/0cb28436f67bb42690ee#emscripten)**を参考にしてください。

`source ~/emsdk/emsdk_env.sh`

次のようにしてコンパイルします。

`emcc hello.ll -s WASM=1 -o hello.html`

これによって生成された`hello.html`、`hello.js`、`hello.wasm`をウェブサーバに置いて、ウェブブラウザを開発者モードにし、hello.htmlにアクセスすると、コンソールに`Hello, world`が表示されます。

## LLVM IRを生成する

LLVM IRを生成するのは、字句解析後に抽象構文木を構築するのとだいたい同じです。これをLLVMのAPI関数を利用して行うのですが、これがなかなか面倒です。

次のような、

``` .ll
; ModuleID = 'mymodule'
source_filename = "mymodule"

define i32 @main() {
entry:
  ret i32 0
}
```

何もしないで`0`を返すだけの`main`関数を出力してみましょう。

``` .cpp
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/BasicBlock.h>
#include <llvm/IR/Instruction.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/Constant.h>
#include <llvm/IR/Constants.h>
#include <llvm/Support/raw_os_ostream.h>

using namespace llvm;

int main()
{
	LLVMContext llvmcx;
	Module *module;
	Function *current_function;
	BasicBlock *current_block;

	module = new Module("mymodule", llvmcx);
	DataLayout dl(module);

	// main関数を作成（戻り値int、引数なし）
	current_function = Function::Create(FunctionType::get(Type::getInt32Ty(llvmcx), false), GlobalVariable::ExternalLinkage, "main", module);

	// ブロックを作成
	current_block = BasicBlock::Create(llvmcx, "entry", current_function);

	// 関数の内容を構築


	// 普通はここでブロックの中に様々な命令を構築する


	// return 0
	ReturnInst::Create(llvmcx, ConstantInt::get(Type::getInt32Ty(llvmcx), 0), current_block);

	// LLVM IR を出力
	std::string ll;
	raw_string_ostream o(ll);
	module->print(o, nullptr);
	o.flush();

	puts(ll.c_str());
	return 0;
}
```

コンテキストを作って、モジュールを作って、ファンクションを作って、ベーシックブロックを作って、return命令を作って、文字列で出力するだけなのですが、たったそれだけのLLVM IRを生成するのに、この分量のコードが必要になります。

## orelangからLLVM IRを生成する

自作言語からLLVM IRを生成すればいいのですが、それがとても骨の折れる作業です。

...という記事を、3年前に書きました。

▼Orelang(俺言語) の LLVM IR コンパイラを作ってみた
https://qiita.com/soramimi_jp/items/b7a0a9de381f3c320fe6

JSONテキストをパースして、LLVMのAPIを用いて構文木を構築し、LLVM IRを出力します。あとは、emsdkのclangにかけてコンパイルし、ウェブサーバにアップすれば完成です。


orelangコンパイラのソースコードは筆者のリポジトリにあります。

https://github.com/soramimi/orec

コンパイラと言っても、ソースコード埋め込みのJSONからLLVM IRを生成するだけなので、コンパイラを名乗るのはおこがましい気がしますが、コンパイラのフロントエンドということで許してください。

実行手順を示します。

```
soramimi@ubuntu18x64:~$ git clone https://github.com/soramimi/orec.git
Cloning into 'orec'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 32 (delta 0), reused 3 (delta 0), pack-reused 27
Unpacking objects: 100% (32/32), done.
soramimi@ubuntu18x64:~$ source ~/emsdk/emsdk_env.sh 
Adding directories to PATH:
PATH += /home/soramimi/emsdk
PATH += /home/soramimi/emsdk/clang/e1.38.30_64bit
PATH += /home/soramimi/emsdk/node/8.9.1_64bit/bin
PATH += /home/soramimi/emsdk/emscripten/1.38.30

Setting environment variables:
EMSDK = /home/soramimi/emsdk
EM_CONFIG = /home/soramimi/emsdk/.emscripten
EM_CACHE = /home/soramimi/emsdk/.emscripten_cache
LLVM_ROOT = /home/soramimi/emsdk/clang/e1.38.30_64bit
EMSCRIPTEN_NATIVE_OPTIMIZER = /home/soramimi/emsdk/clang/e1.38.30_64bit/optimizer
BINARYEN_ROOT = /home/soramimi/emsdk/clang/e1.38.30_64bit/binaryen
EMSDK_NODE = /home/soramimi/emsdk/node/8.9.1_64bit/bin/node
EMSCRIPTEN = /home/soramimi/emsdk/emscripten/1.38.30

soramimi@ubuntu18x64:~$ cd orec/
soramimi@ubuntu18x64:~/orec$ make
g++ -std=c++11 -I/usr/lib/llvm-6.0/include   -c -o main.o main.cpp
g++ -std=c++11 -I/usr/lib/llvm-6.0/include   -c -o json.o json.cpp
g++ main.o json.o -o orec -L/usr/lib/llvm-6.0/lib `/usr/bin/llvm-config-6.0 --libs`
soramimi@ubuntu18x64:~/orec$ ./orec >test.ll
soramimi@ubuntu18x64:~/orec$ emcc test.ll -s WASM=1 -o test.html
soramimi@ubuntu18x64:~/orec$ cp test.html /var/www/html/wasmtest/index.html
soramimi@ubuntu18x64:~/orec$ cp test.js /var/www/html/wasmtest/
soramimi@ubuntu18x64:~/orec$ cp test.wasm /var/www/html/wasmtest/
```

HTMLファイルを生成するために `emcc test.ll -s WASM=1 -o test.html` というコマンドを実行してから、test.html を index.html としてコピーしました。それ以降は `emcc test.ll -o test.js` を実行すれば、`.js`と`.wasm`だけ生成されるようになります。これら3つのファイルをウェブサーバに配置し、ウェブブラウザからアクセスすれば、orelangの実行結果が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61222/7f5fd2bc-7387-42c1-c1f2-d9a119af255b.png)