HN 日本語サマリー

← 一覧へ戻る
プログラミング

Minikotlin: ブラウザ内でKotlinをWebAssemblyにコンパイルする

Minikotlin (minikotlin.run)

64 pointsby frizlab13 コメント

要約

Minikotlinは、C言語でゼロから書かれたKotlinコンパイラで、JVMやLLVMなどの外部ツールに依存せず、直接WebAssembly GCバイトコードを生成します。コンパイラ自体もWASMにコンパイルされるため、ブラウザタブ内でKotlinソースコードを入力として受け取り、実行可能なWASMモジュールを出力できます。これにより、ツールチェーンのインストール不要で、ブラウザ上でエンドツーエンドのコンパイルと実行が可能になります。

全文翻訳

Kotlin → WebAssembly · Cで書かれたコンパイラブラウザタブで動作するKotlinコンパイラ。minikotlinはC言語でゼロから書かれており、手作業でWebAssembly GCバイトコードを生成します — JVM、LLVM、Binaryen、Gradleは一切使用しません。コンパイラ自体がWASMにコンパイルされるため、.ktソースを入力すると、タブ内で完全に実行可能な.wasmモジュールが出力されます。 Studioを開く specimen backendWASM-GCstructs · call_ref · EH servernoneruns client-side end-to-end tests366frontend: 657 runtime deps0nothing installed greeter — minikotlin Studio RUN 12345678 // Main.kt + Greeter.ktを1つのユニットとしてコンパイルします fun main() { val g = Greeter("WebAssembly") println(g.greet()) (1..3).forEach { println("tick $it") } } build 2 .kt → main.wasm · ok, 41ms Hello, WebAssembly tick 1 tick 2 tick 3 the pipeline .kt→ lex→ parse→ sema→ HIR→ MIR→ WASM-GC→ run 01 1パスで、バイトコードまで全て処理します。中間VMや外部バックエンドはありません。フロントエンド — レキサー、パーサー、セマンティックアナライザ(mkfと呼ばれます) — は、WASM-GCを手書きする前に、独自のIRを2つ経由します。 input Kotlin source 複数の.ktファイルが1つのユニットとしてコンパイルされるため、互いに参照できます。 frontend · mkf lex · parse · sema 名前、型、スマートキャストが解決されます。657のフロントエンドテスト。 high IR HIR 言語に近い、デシュガーされた型付きツリー。 mid IR MIR オペレーション、ローカル変数、構造体レイアウト、vtableに低レベル化されます。 codegen WASM-GC バイトコードが直接生成されます。LLVMやBinaryenはループに含まれません。 output main.wasm 同じブラウザタブでインスタンス化され、実行されます。 コンパイラ自体がWASMとして提供されるため、コードが実行される場所で動作します — インストールするツールチェーンはありません。 02 今日サポートされているKotlin。 トークンのサブセットではありません。これらはWASM-GCの型システムに適切に低レベル化されており、それぞれにエンドツーエンドのテストがあります。 Classes & objects オブジェクトモデル 継承(open/override)、デフォルトメソッドを持つインターフェース、生成されたequals/hashCode/copyを持つデータクラス、enum、名前付き、コンパニオン、匿名オブジェクト式。 Sealed & smart-casts 制御フロー 網羅的なwhenとisチェックを持つsealed階層はref.testにコンパイルされ、フローセンシティブなスマートキャストはブランチをまたいで保持されます。 Null safety 型 ull許容型のエンドツーエンド — ?.セーフコール、?:エルビス演算子、!!アサーション — プリミティブ型もnull許容で、Any経由でボックス化されます。 Generics 型 関数とクラスの型パラメータ — fun <T> id(x: T): T — ボックス化されたAny表現に低レベル化されます。 Operators & extensions エルゴノミクス 演算子オーバーロード(plus, get, …)はLHSクラスにディスパッチされ、拡張関数は独自の名前空間に、カスタムアクセサはバックフィールドを持ちます。 Coroutines ノンブロッキングなlaunch、delay、coroutineScope — 非同期変換(Asyncify)やJSPI、スレッドなしで、実際のサスペンションがクロージャ上のCPSとしてコンパイルされます。 Standard library 手書きのString/Char操作、リストの高階関数(map/filter/forEach…)、kotlin.math、スコープ関数let/apply/run/also/with。 03 KotlinのアイデアがWASM命令になるまで。 低レベル化は、あらゆるコンパイラにおける興味深い部分です。4つの実際の低レベル化ステップ — それぞれが言語構成要素を具体的なWASM-GCメカニズムにマッピングし、手作業で記述されています。 L.01 class instance → struct.new すべてのクラスはGC構造体型になり、プロパティは実際の構造体フィールドになります。アロケーションは、手作りのバイトヒープではなく、struct.newです。 L.02 virtual call → call_ref openおよびoverrideされたメソッドは、クラスごとのvtableを経由します。仮想呼び出しは、関数参照のロードとそれに続くcall_ref — 真の動的ディスパッチです。 L.03 type check → ref.test isチェックとwhen (x) { is T -> }のアームはref.testにコンパイルされ、絞り込まれた値はref.castを通じて再利用されます — スマートキャストが無料で利用できます。 L.04 coroutine → CPS closure サスペンションポイントは関数をその境界で分割し、残りを継続としてキャプチャします。単純なdelayはホストにトークンを渡し、setTimeoutから再開します — スタックから完全に外れます。 04 サンプルコードのコンパイルと実行。 以下はすべてサポートされているKotlinです。Studioはコンパイラ自身のレキサーでこれをハイライトし、結果のWASMをその場で実行します。 Race.kt import kotlinx.coroutines.* sealed class Lane(val id: Int) class Fast : Lane(1) class Slow : Lane(2) fun Lane.pace(): Long = when (this) { is Fast -> 120 is Slow -> 300 } fun main() = runBlocking { val lanes = listOf(Fast(), Slow()) coroutineScope { lanes.forEach { launch { delay(lane.pace()) println("lane ${lane.id} in") } } } println("race over") } 2つのコルーチンが実際にレースしています。各launchはdelayでサスペンドし、制御を譲ります。速いレーンが先に再開し、coroutineScopeは最後の行が実行される前に両方のチャイルドコルーチンの完了を待ちます。ブロッキングもAsyncifyもなし — サスペンションは継続クロージャにコンパイルされます。sealedクラスLane、when (this) { is … }ディスパッチ、Lane.pace()拡張機能はすべて、解釈ではなく、実際に低レベル化されています。 > lane 1 in > lane 2 in > race over