LibJSのざっくりとしたアーキテクチャと読み方
LibJSをなんとなく読んでみて、ざっくりアーキテクチャがつかめたのでメモ。V8やJSCなどのメジャーJSエンジンと違ってJITや大量のファストパスや独自言語がない[1]ので読みやすい。
LibJSは、まあなんというかよくある感じのバイトコードインタプリタとして実装されている。
そうすると、
- ソースコードをパースしてASTを作るところ
- ASTをコンパイルしてバイトコードを作るところ
- バイトコードの列を実行するところ
がわかれば、まあなんとなくの動きを追いかけやすくなる。
パース
まず 1. ソースコードをパースしてASTを作るところ は、実行コンテクストがスクリプトがモジュールかによるが、
などにある。これらの関数が、Parser.cpp や Parser.h で実装されている手書きの再帰下降構文解析の関数を呼び出している。
コンパイル
次に、ASTをバイトコードにコンパイルするのは Bytecode/Generator.h の Bytecode::Generator::generate_from_ast_node だ。 generate_from_ast_node 関数内で呼び出している compile 関数 が本質で、この関数が AST をトラバースしてバイトコード列にコンパイルしている。
実行可能なバイトコードの列は Executable と呼ばれる。Executable内のバイトコードの列は Vector<u8> 型として定義されている。Vector は C++ の std::vector みたいなもので、u8 は uint8_t みたいなものである(おそらく、後述する Instruction クラスのインスタンスが u8 に収まるようになっているんだと思う)。
バイトコードインタプリタ
バイトコードインタプリタは、Bytecode/Interpreter.cpp の run_executable 関数 が起点になっている。そして、その中で呼び出されている run_bytecode 内で各バイトコードに対するディスパッチをしている。
また Bytecode/Instruction.h を見ると、バイトコード命令の一覧がわかる。各命令に対応するクラスの基底クラスとなる Instruction クラスも、このファイル内で定義されている。
さらに Bytecode/Op.h を見ると Instruction クラスを継承した各命令のクラスの定義がある。ここを見ると、それぞれの命令がどういうオペランドを取るのかがわかる。
おわりに
これらが明らかになっているとLibJSの特定の箇所を読んでいくのに便利なので、メモとして残した。本当にサッと読んで構造だけ把握した感じなので間違ったことも書いてあるかもしれない。なので、LibJSに詳しい人からのツッコミ大歓迎。
-
V8は一部のコードがTorqueで、JSCの場合はLLIntがofflineasmで書かれているので、初見だとびっくりする ↩︎
Discussion