コンパイラを新規作成するなら書き換え不能な変数をデフォルトにするべし
LLVMは型に厳しいアセンブラです。なので、型をきっちり合わせないと怒られます。
今までJavaのバイトコードに似た内部コードをLLVMに変換する形でコンパイラを作ってきました。
そして、そこそこのプログラムは動作するようにはなりました。
だけど関数ポインタの扱いが怪しくてどう型を扱うのが良いのかを考えなくてはならなくなりました。
そこでLLVMでの型を見直し次のような事がわかりました。
関数は既にアドレスが割り当てられた物なのでポインタとして扱われています。
関数ポインタは関数のアドレスを入れる物だからポインタのポインタとして扱われています。
書き換え可能な変数はalloca(スタックから領域を確保する命令)したアドレスに格納します。型はアドレスなのでポインタです。Javaの変数もstoreしたりloadしたりしますが型がポインタという意識をしたことは無かった所です。
store,loadが出来ると言う事は値が変更される可能性があると言う事です。
アドレスが存在するのでポインタを取り出せます。
書き換えが出来ない値は出来ればメモリ上に配置したない値です。
レジスタが溢れたらメモリ上に移動し結果としてアドレスが割り振られる事があるかもしれませんが、基本的にはメモリ上に存在して欲しく無い値です。
値が変わったりポインタを取り出したりするか、値が変わらないかによって最適化具合が変わるのです。値が変わらない変数が最初から分かっていれば最適化フェーズの仕事は減る訳です。
C言語の文化では、define値で定数を決めていました。
マクロなのでプログラム中に直接値が書き込まれる事が明らかでした。
変数は、メモリ上に取られる意識がありました。何故か知らないけど、最適化コンパイラでは変数がレジスタ上にのみ存在するようになることもあるという意識だったと思います。
変わらない値は、出来るだけメモリ上に取って欲しくないという意識の現れあるべきです。
今作ろうとしている言語は型付きの高級アセンブラです。なので、LLVMに合わせた考え方をするのがよいはずです。値変更可能な変数はポインタとして扱い、値変更不能な変数はそのままの型として扱うのがよさそうです。つまり、今まで作って来たコンパイラの考え方は大分ずれているように思えます。
むしろ、関数型言語のほうが近いんじゃないかと。
関数の引数が書き換えられなくて、一度書き換え可能な変数に入れてからじゃないといけないとか。
まさにそんなかんじなわけです。
高速にしたいなら、出来るだけ、書き換え不能な変数だけ使う方が良いと。
ということなので、LLVM用コンパイラ作成入門を書くなら、関数型言語になるんだろうなと。
def main() { val a = 1 + 2 val b = a * 3 // a=a+1 error print_i(b) }
みたいな言語が基本で、書き換えたかったら
def main() { val a:Ptr[int] a[0] = 1 + 2 val b:int = a[0] * 3 print_i(b) }
みたいなかんじ。うー、書きにくいけど、Cレベルな言語なんだよね。
これが、アセンブラに近い。*を使うといかのように書ける。
def main() { val a:Ptr[int] *a = 1 + 2 val b:int = *a * 3 print_i(b) }
で、シンタックスシュガーとして、varが使える。
def main() { var a:int = 1 + 2 val b:int = a * 3 print_i(b) }
と書けるという理解になるといいのかな?