Compiler · WASM GC · WASM EH
Zero dependencies · No JVM · No LLVM

Kotlin, compiled in your browser.

A from-scratch Kotlin pipeline written in C — lexer · parser · sema · HIR · MIR · WASM — that emits WASM GC structs for class instances, vtables + call_ref for virtual dispatch, and WASM EH tags for try/catch. No JVM bytecode, no Compose runtime, no Java interop — just the Kotlin language core, compiled directly to a WASM module your browser can run.

Live · 27 e2e tests pass in your tab

Real Kotlin, real WASM, real polymorphism.

Open classes, override, interface dispatch, nullable refs with ?. and ?:, default args, data classes, try/catch — all going through a proper IR pipeline and out as WASM GC bytes. Sample below: a polymorphic callSound dispatched through a vtable.

poly.kt vtable · call_ref · upcast
open class Animal {
    open fun sound(): Int = 0
}
class Dog : Animal() {
    override fun sound(): Int = 1
}
class Cat : Animal() {
    override fun sound(): Int = 2
}

// Receiver typed Animal — virtual dispatch via vtable.
fun callSound(a: Animal): Int = a.sound()

fun testPoly(): Int =
    callSound(Dog()) + callSound(Cat()) + callSound(Animal())
testPoly() = 3      // 1 (Dog) + 2 (Cat) + 0 (Animal)

Same source → run it yourself in the playground (single-sample editor / output split), or open the full test suite and watch all 27 compiled .wasm modules instantiate via WebAssembly.instantiate against the same protocol the Node CI uses.

27
e2e tests · 100% pass
classes · null · try · enum
657
frontend tests
mkf — lex · parse · sema
F
phases shipped (B → F)
int · ctrl · class · iface · null · try
0
dependencies
no jvm · no llvm · no compose

What's compiled today

Classes via WASM GC

Every Kotlin class lowers to a real WASM GC struct — primary-ctor properties, val / var fields, this.field reads/writes. No linear-memory bump allocator, no shadow stack — the engine's GC tracks instances natively.

Inheritance + open

class Sub : Base() emits proper subtype declarations. Field layouts inherit; (ref Sub) flows where (ref Base) is expected via WASM GC's structural subtyping — no runtime cast needed.

Virtual dispatch via call_ref

Each open class gets a per-class vtable struct (subtyped down the chain) and a vtable global initialized via ref.func. Method calls load the receiver's __vt and call_ref the slot — true polymorphism end-to-end.

Interfaces (single-impl)

interface I { fun m(): Int } compiles to an abstract base; class C : I overrides via the same vtable machinery. Single-interface implementations dispatch polymorphically through an interface-typed param.

Nullability with ?. / ?:

null literal → ref.null; b == nullref.is_null; ?. and ?: lower to block-as-expression with a hidden temp + null-check if. Ref-typed chain like o?.inner ?: Inner(-1) works end-to-end.

Default args

fun f(x: Int = 1) — call-site splicing. Each missing arg lowers to the captured default expression as if inlined. No runtime $default wrapper, no bitmask — constants fold straight through.

try / catch via WASM EH

Each thrown class gets its own tag with signature (ref null $Class) → (). throw emits the EH proposal's throw op; try { } catch (e: T) { } compiles to a try_table + nested handler block dispatching by tag.

when / for / while

when (x) { 1 -> ...; in 0..9 -> ...; else -> ... } desugars to a nested if-cascade. for (i in lo..hi) specializes to a while with i++. Mutable locals, structured WASM block/loop/br_if — no Relooper needed.

Companion + object

companion object { val DEFAULT = 42 } and top-level object Singleton { val X = 7 } get their properties promoted to mangled module globals. Box.DEFAULT resolves to a static read.

Compiler pipeline

.kt
Lexer
Parser
Sema
HIR
MIR
WASM
.wasm

mkf (Mini Kotlin Frontend) handles the first three stages — tokenisation, parsing, type resolution; vendored into the backend as a static library. HIR stays Kotlin-aware (smart casts, when-cascades, lambdas-as-expression), MIR is mutable-locals three-address code that maps one-to-one onto WASM local.get / local.set, and the codegen emits binary struct.new, call_ref, try_table, and friends directly — no wabt, no binaryen.

Honest coverage

Shipped

  • Int + Boolean arithmetic, comparison, equality
  • Top-level val, fun bodies
  • if/else, when, while, for (i in lo..hi)
  • Local val/var, mutation, recursion
  • class with primary ctor, val/var fields, methods, this
  • open / override with vtable dispatch
  • interface (single impl) via the same vtable path
  • Companion object + top-level object property promotion
  • Nullable refs, null literal, ?., ?:
  • Default args, data class, enum class entries
  • throw / try/catch via WASM EH

Roadmap

  • String + heap (UTF-16 array16, no Compose)
  • Operator overloading (+, .., in)
  • String templates ("x = $x")
  • Lambda → synthetic FunctionN class
  • Collections — List, Map, iterator protocol
  • Extension fns + scope fns (let, run, apply)
  • Multi-interface impl (real itable layout)
  • Sync host bridge (println, performance.now)
  • Coroutines, inline+reified, reflection — v2