MEP 50. Mochi-to-Kotlin transpiler: Kotlin Multiplatform via Gradle, coroutine + Channel agents, Android .aab, K/Native single binaries
| Field | Value |
|---|---|
| MEP | 50 |
| Title | Mochi-to-Kotlin transpiler |
| Author | Mochi core |
| Status | Draft |
| Type | Standards Track |
| Created | 2026-05-23 10:30 (GMT+7) |
| Depends | MEP-4 (Type System), MEP-5 (Type Inference), MEP-13 (ADTs and Match), MEP-45 (C transpiler, IR reuse), MEP-46 (BEAM transpiler, IR reuse), MEP-47 (JVM transpiler, IR reuse but distinct from MEP-50), MEP-48 (.NET transpiler, IR reuse), MEP-49 (Swift transpiler, IR reuse) |
| Research | ~/notes/Spec/0050/01..12 |
| Tracking | /docs/implementation/0050/ |
Abstract
Mochi today ships vm3 (mochi run), an ahead-of-time C transpiler producing native single-file binaries (MEP-45), an Erlang/BEAM transpiler producing supervised concurrent runtimes (MEP-46), a JVM transpiler that emits Java bytecode directly via ASM and reaches Maven Central and Project Loom (MEP-47), a .NET transpiler that emits C# source and reaches NuGet and NativeAOT (MEP-48), and a Swift transpiler that emits Swift 6.0 source and reaches the Apple App Store, server-side Swift, and Static Linux SDK binaries (MEP-49). None of these paths produces a first-class Android application, a Jetpack Compose UI, a Kotlin Multiplatform iOS / desktop binary built from shared sources, or a Kotlin/Wasm browser bundle. The Android ecosystem (3.6B active devices) requires Kotlin or Java compiled by Google's Android Gradle Plugin, signed via apksigner, and distributed through Google Play Console as an .aab Android App Bundle. Kotlin Multiplatform reached Stable in 2023-11 and is now the load-bearing surface for cross-platform mobile, desktop, web, and server code from a single Kotlin codebase. MEP-50 specifies a sixth transpiler pipeline that targets Kotlin 2.1's K2 compiler, packages output through Gradle and the Kotlin Multiplatform Gradle Plugin, and reaches every KMP target: JVM, Android, Kotlin/Native (iOS, macOS, Linux, Windows), Kotlin/JS (browser, Node.js), and Kotlin/Wasm.
The pipeline reuses MEP-45's typed-AST and aotir IR, plus the monomorphisation, match-to-decision-tree, closure-conversion, exhaustiveness-check, and sendability-inference passes shared with MEP-46, MEP-47, MEP-48, and MEP-49. It forks at the emit stage: instead of emitting ISO C23 (MEP-45), Core Erlang via cerl (MEP-46), JVM bytecode via ASM (MEP-47), C# source via Roslyn SyntaxFactory (MEP-48), or Swift 6.0 source via a Mochi-side syntax tree (MEP-49), it emits Kotlin 2.1 source text via KotlinPoet (Square's KotlinPoet 1.18+, the canonical Kotlin-source emitter), optionally piped through ktfmt or ktlint for canonical layout. Eleven packaging targets ship together: --target=kotlin-jvm produces a .jar runnable on any Java 17+ runtime (~3-8 MB with kotlin-stdlib included); --target=kotlin-android produces a signed .aab Android App Bundle (or .apk debug build) via AGP 8.7+, ready for Play Console upload; --target=kotlin-ios-arm64, --target=kotlin-macos-arm64, --target=kotlin-linux-x64, --target=kotlin-linux-arm64, and --target=kotlin-windows-x64 produce Kotlin/Native single binaries (~5-15 MB, statically linked against the K/Native runtime); --target=kotlin-js-browser and --target=kotlin-js-node produce ES-module JavaScript bundles; --target=kotlin-wasm-js produces a Wasm GC bundle (shipped as Alpha with a stability caveat); --target=kotlin-kmp-library produces a multi-target KMP library consumable from another Kotlin project. One codegen pass feeds all KMP targets via per-target source sets (commonMain, jvmMain, androidMain, iosMain, macosMain, linuxMain, mingwMain, jsMain, wasmJsMain).
The master correctness gate is byte-equal stdout from the produced binary (executable Kotlin target run directly, Android app run in the Android emulator with stdout captured via instrumented test, JS bundle run via Node.js, Wasm bundle run via wasmtime or Node.js with Wasm GC support) versus vm3 on the entire fixture corpus, across Kotlin 2.1.0 and Kotlin 2.1.20, on x86_64-linux-gnu, aarch64-linux-gnu, aarch64-darwin, and x86_64-windows. vm3 is the recording oracle for expect.txt; the transpiler does not link against or depend on vm3.
Five load-bearing decisions:
-
Kotlin 2.1 floor with the K2 compiler. Kotlin 2.0 (released 2024-05-21) was the first release where the K2 frontend (a complete rewrite of the legacy K1 frontend) reached stable status; Kotlin 2.1 (released 2024-11-27) added multidollar string interpolation, smart-cast improvements for sealed
whenexpressions, and refined KMP source-set defaults. MEP-50 emits exclusively against Kotlin 2.1+ language mode. This locks in static guarantees that Mochi's type system already proves at the Mochi level (exhaustivewhenover sealed interfaces, sendability of channel payloads viaSendablemarkers, nominal error types viasealedhierarchies) and prevents the codegen pass from emitting code that compiles under K1 but breaks under K2. Floor-ing at 2.1 drops Kotlin 1.9 (last K1-frontend release, end-of-life by 2026 H2). Kotlin 1.9 is rejected because the K2 compiler unlocks the smart-cast improvements that Mochi's lowered code relies on; backporting via flags creates a dual codepath. See 02-design-philosophy §3, 12-risks-and-alternatives §R1, §A2. -
Kotlin Multiplatform full, not JVM-only. MEP-47 already owns the JVM target via direct bytecode emission (skip kotlinc, skip javac, ASM-direct). MEP-50's reason to exist is the KMP surface: a single Mochi codebase compiles to JVM, Android, Kotlin/Native (iOS arm64, macOS arm64, Linux x64/arm64, Windows x64), Kotlin/JS (browser, Node.js), and Kotlin/Wasm (Wasm GC). Excluding any of these targets would force users with a Mochi backend and a KMP-mobile frontend to switch Mochi targets per platform, breaking the single-language story KMP was designed to enable. The cost is a 5-OS-by-3-arch CI matrix plus the KMP plugin's gradle plumbing, comparable to MEP-49 (Swift) and MEP-48 (.NET). On the JVM target the two flows (MEP-47 direct bytecode vs MEP-50 source-to-kotlinc) produce different bytecode; users can pick either via the
--target=kotlin-jvmvs--target=jvmdistinction. The cross-target differential gate (TestCrossTargetDifferential) verifies both produce byte-equal stdout on every shared fixture. See 02-design-philosophy §4, 07-kotlin-target-portability §1-9. -
Mochi agents as custom actor class wrapping
Channel<Message>plus structured concurrency. kotlinx.coroutines (1.10+) providesChannel<T>(a bounded or unbounded coroutine-safe queue),CoroutineScope,SupervisorJob(a job whose children failures do not cancel siblings), andDispatchers.Default(a shared worker pool sized to CPU count). Mochi agents lower one-to-one: an agent declaration becomes apublic classwith aprivate val mailbox: Channel<Message>and aprivate val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default); the mailbox loop is ascope.launch { for (msg in mailbox) handle(msg) }started ininit;cast(msg)ismailbox.trySend(msg)from a non-suspending function ormailbox.send(msg)from asuspendfunction;call(req) -> replyuses a one-shotCompletableDeferred<Reply>carried as a field on the message data class. The kotlinx.coroutinesactor { }builder is explicitly not used because it has been deprecated in the ROADMAP since 2017 (kotlinx.coroutines issue #87) and pending replacement since 1.7; the custom-class shape is what the kotlinx.coroutines maintainers recommend. No GCD, no Reactor, no Akka-Kotlin, no third-party actor framework. See 02-design-philosophy §5, 09-agent-streams §1-§12. -
Reuse MEP-45's
aotirIR plus all shared passes. The IR is target-agnostic; monomorphisation, match-to-decision-tree, closure-conversion, exhaustiveness checking, and sendability inference all run once and feed six backends. The fork is at the emit pass:transpiler3/kotlin/lower/lowersaotirto Kotlin-source structural nodes;transpiler3/c/emit/lowersaotirto C;transpiler3/beam/lower/to Core Erlang;transpiler3/jvm/lower/to JVM bytecode;transpiler3/dotnet/lower/to C# source;transpiler3/swift/lower/to Swift source. Sharing the IR keeps the six targets semantically aligned and amortises pass-implementation work. See 05-codegen-design §5. -
kotlinx. family as fat runtime;
MochiRuntimeas thin KMP library.* The runtime libraryMochiRuntime(Apache-2.0, ~6000 LOC) provides only what the kotlinx.* package family does not: a Mochi-typed function-interface zoo (handled by Kotlin function types directly), Mochi-shaped collection helpers re-exportingPersistentList/PersistentMap/PersistentSetfromkotlinx.collections.immutable, a small Datalog engine, AI / FFI dispatch tables, agent supervisor, ZonedDateTime helpers abovekotlinx.datetime.Instant(since Foundation-style date manipulation lives inkotlinx.datetime), and aJSONValuesealed-interface ADT for dynamic JSON overkotlinx.serialization.json.JsonElement. Everything else (HTTP via Ktor client, file I/O via okio or platform stdlib, regex via KotlinRegex, locale, dates viakotlinx.datetime) goes through Kotlin first-party libraries directly. Square Retrofit, OkHttp, Moshi, Gson, RxKotlin are explicitly rejected as runtime deps. See 04-runtime §1-§20, 02-design-philosophy §8 and §13.
The gate for each delivery phase is empirical: every Mochi source file in tests/transpiler3/kotlin/fixtures/ must compile via the Kotlin pipeline and produce stdout that diffs clean against the expect.txt recorded by vm3. kotlinc-clean (with -Werror -Xexplicit-api=strict -language-version=2.1 -api-version=2.1) on generated code is the secondary gate. ktfmt fixed-point (running format twice produces no diff) is the tertiary gate. Android App Bundle validation via bundletool validate is the quaternary gate. Kotlin/Native single-binary build is the quinary gate. Reproducibility (bit-identical .jar and .aab across two CI hosts) is the senary gate. Google Play Console pre-launch validation (via gcloud firebase test android run) is the septenary gate.
Motivation
Mochi today targets vm3 (for mochi run), MEP-45 (C, single-binary AOT), MEP-46 (BEAM, supervision and hot reload), MEP-47 (JVM, Maven Central via direct bytecode and Loom), MEP-48 (.NET, NuGet and NativeAOT), and MEP-49 (Swift, App Store and Static Linux SDK). None deliver the Android platform, the Kotlin Multiplatform iOS/desktop shared-codebase story, Jetpack Compose UI, Kotlin/JS (the canonical Kotlin browser target), or Kotlin/Wasm:
-
Google Play and Android. Android apps require Kotlin or Java compiled by the Android Gradle Plugin, dex'd by D8/R8, signed by apksigner, and distributed as an
.aabAndroid App Bundle through Google Play Console. Sideloading exists but Google Play is the primary distribution channel (3.6B active Android devices, 96% of Android app installs in non-China markets). MEP-47 (JVM bytecode) cannot produce an.aab: the JVM-bytecode target emits.classfiles that lack the Android-specific annotations, resource-merging metadata, manifest synthesis, and lifecycle-aware code that AGP injects. MEP-48 (.NET) reaches Android via .NET MAUI but ships the Mono runtime (~30-50 MB footprint, JIT-incompatible with Google Play's app-size guidance). MEP-49 (Swift) does not target Android at all (Swift on Android is unsupported by Apple). MEP-50 produces idiomatic Kotlin indistinguishable from hand-written Android code after ktfmt, sized at 3-8 MB after R8 minification, free of any embedded runtime evaluator, ready for Play Console upload. -
Kotlin Multiplatform: one codebase, every target. KMP reached Stable in 2023-11 (Kotlin 1.9.20 release notes; further refined in 2.0 and 2.1). The promise is: write shared logic in
commonMainKotlin, expose it viaexpect/actualboundary declarations, compile to JVM bytecode, Android dex, Kotlin/Native machine code, Kotlin/JS, or Kotlin/Wasm from one source tree. Mochi'saotirIR is already target-agnostic; emitting Kotlin source feeds every KMP target without a per-target Mochi codegen pass. A Mochi user with iOS + Android + web + server backend has one Mochi source tree producing five binaries, all bit-equal in semantics. -
Jetpack Compose Multiplatform. Google's Jetpack Compose (canonical Android UI since 2021) and JetBrains' Compose Multiplatform 1.7+ (Android + iOS + macOS + Linux + Windows + web, since 2024) are the declarative-UI story for Kotlin. While MEP-50 v1 does not expose Compose as Mochi language surface (it remains a v2 candidate), Mochi-emitted Kotlin code is consumable from Compose projects: users import the Mochi-generated KMP module and call Mochi functions from their
@Composableviews. -
Server-side Kotlin via Ktor and Spring. Ktor 3.0 (released 2024-10) is JetBrains' coroutine-native Kotlin web framework, used in production at JetBrains, Adobe, and a number of Fortune 500 backends. Spring Boot 3.4 (2024-11) supports Kotlin 2.1 as a first-class language. A Mochi user with a Kotlin Android frontend and a Mochi backend can now keep one language across the stack, with Ktor as the HTTP layer.
-
Kotlin/Native single binaries. Kotlin/Native (LLVM-backed, post-1.9 with the new memory model as default) produces statically linked single binaries comparable to Go or Rust output. Targets: iosArm64, iosSimulatorArm64, macosArm64, macosX64, linuxArm64, linuxX64, mingwX64 (Windows), watchosArm64, tvosArm64. Mochi
--target=kotlin-linux-x64produces a single-binary Linux executable;--target=kotlin-ios-arm64produces a Kotlin/Native iOS framework consumable from Swift / Objective-C. The 2021-era K/Native freeze model is not supported (incompatible with Mochi's shared-state agent model); the new memory model (default since Kotlin 1.9) is required. -
Kotlin/JS and Kotlin/Wasm. Kotlin/JS (IR backend, since Kotlin 1.9; legacy backend removed) produces ES module JavaScript ready for npm publish or direct browser load. Kotlin/Wasm (Wasm GC, marked Beta in 2.1.20) produces a Wasm module that runs on browsers with Wasm GC enabled (Chrome 119+, Firefox 120+, Safari 18.2+) plus Node.js with the experimental flag. Mochi reaches the browser, Node.js, and the modern Wasm GC target through one codegen pass.
-
JetBrains tooling. IntelliJ IDEA (the dominant Kotlin IDE), Android Studio (built on IntelliJ Platform), and Fleet provide first-class Kotlin support: K2-aware refactoring, inline hints, multiplatform navigation, KMP wizards. Mochi-emitted Kotlin opens cleanly in these tools, with full IDE inference and debugger support, because the emitted source is structurally indistinguishable from hand-written Kotlin.
-
Sealed interfaces (since Kotlin 1.7) and the K2 smart-cast improvements (Kotlin 2.0+). Mochi sum types map directly to Kotlin sealed interfaces with
data classanddata objectvariants. K2's smart-cast improvements make exhaustivewhenover sealed hierarchies a compile-time guarantee identical to Mochi's match-exhaustiveness check. The two type systems align; the Kotlin compiler becomes a second-line check on Mochi's correctness. -
kotlinx.coroutines: the canonical async surface. kotlinx.coroutines 1.10+ provides structured concurrency (parent-child Job hierarchies, automatic cancellation propagation),
Flow<T>(a cold async sequence with backpressure),Channel<T>(a coroutine-safe queue with bounded / unbounded / conflated / rendezvous policies),SharedFlow<T>andStateFlow<T>(hot variants), plus dispatchers (Dispatchers.Mainfor UI,Dispatchers.IOfor blocking I/O,Dispatchers.Defaultfor CPU). Mochi's agent, stream, async, and parallel primitives lower one-to-one. Compare Swiftactor + AsyncStream(similar but Apple-only), .NETIAsyncEnumerable(similar but more verbose), BEAM GenStage (different paradigm). Kotlin's syntax and semantics are the closest to Mochi's own after Swift. -
Maven Central and Gradle. Kotlin libraries publish to Maven Central (the canonical Java/Kotlin package registry), with Gradle 8.11+ as the canonical build tool. Mochi's runtime library
MochiRuntimepublishes as a KMP artifact (with-jvm,-android,-iosarm64,-iossimulatorarm64,-macosarm64,-linuxx64,-linuxarm64,-mingwx64,-js,-wasm-jsper-target classifiers) so any KMP project can depend on it viaimplementation("dev.mochilang:mochi-runtime:1.0.0").
The C target (MEP-45) remains the right choice for embedded targets, single-file distribution, and minimal runtime footprint. The BEAM target (MEP-46) remains the right choice for hot-reload services, distributed pubsub, and OTP supervision. The JVM target (MEP-47) remains the right choice for Maven Central interop without kotlinc, Loom concurrency without a coroutine library, and direct-bytecode JIT performance. The .NET target (MEP-48) remains the right choice for NuGet, value-type structs without ARC, and Windows-enterprise integration. The Swift target (MEP-49) remains the right choice for Apple platforms (iOS, macOS, watchOS, tvOS, visionOS), App Store distribution, and FoundationModels access. The Kotlin target is the right choice for Android, Kotlin Multiplatform iOS/desktop shared-codebase, Jetpack Compose UI, Ktor server, and Kotlin/Wasm browser. All six ship; the user picks per workload.
Specification
This section is normative. Sub-notes under ~/notes/Spec/0050/01..12 are informative.
1. Pipeline and IR reuse
MEP-50 shares the front-end and aotir passes with MEP-45, MEP-46, MEP-47, MEP-48, and MEP-49 and forks at the emit stage:
Mochi source
| parser (MEP-1/2/3, reused)
v
typed AST
| type checker (MEP-4/5, reused)
v
aotir IR
| monomorphisation pass (shared)
| closure conversion pass (shared)
| match decision tree (shared)
| exhaustiveness check (shared)
| sendability inference (shared)
v
Kotlin source codegen pass (transpiler3/kotlin/lower/)
| emit Kotlin 2.1 source via KotlinPoet (Square 1.18+)
| optional: ktfmt format --in-place
v
.kt files under out/src/<sourceSet>Main/kotlin/<package>/
| emit settings.gradle.kts at out/
| emit build.gradle.kts at out/
| emit gradle/libs.versions.toml at out/
| emit gradle/wrapper/gradle-wrapper.properties at out/
v
./gradlew build (KMP, JVM, JS, Wasm targets)
./gradlew bundleRelease (Android .aab via AGP 8.7+)
./gradlew linkReleaseExecutable<TGT> (K/Native single binaries)
./gradlew jsBrowserProductionWebpack (Kotlin/JS)
./gradlew wasmJsBrowserDistribution (Kotlin/Wasm)
| apksigner / jarsigner (Android, JVM signing)
| bundletool validate (Android .aab)
v
.jar / .aab / .apk / K/Native binary / .js / .wasm / KMP library
Both mochi run (vm3) and mochi build (transpiler3) share the same parser, type system, and IR.
2. Build driver UX
mochi build --target=<TGT> <input.mochi> [-o <output>]
Targets:
kotlin-jvm: produces a runnable.jarfor the JVM (kotlin-stdlib bundled or referenced).kotlin-android: produces a signed.aabAndroid App Bundle via AGP 8.7+ plusbundletool validate. Also produces.apkdebug builds via the--apkflag.kotlin-ios-arm64: produces a Kotlin/Native iOS framework consumable from Swift (XCFramework via the KMP plugin'sXCFrameworktask).kotlin-ios-simulator-arm64: produces a Kotlin/Native iOS simulator framework for Apple Silicon hosts.kotlin-macos-arm64,kotlin-macos-x64: produces a Kotlin/Native macOS single binary.kotlin-linux-x64,kotlin-linux-arm64: produces a Kotlin/Native Linux single binary, statically linked against the K/Native runtime.kotlin-windows-x64(mingwX64): produces a Kotlin/Native Windows .exe.kotlin-js-browser,kotlin-js-node: produces ES module JavaScript bundles via the Kotlin/JS IR backend.kotlin-wasm-js: produces a Wasm GC bundle (Alpha caveat documented in README).kotlin-kmp-library: produces a multi-target KMP library consumable from another Kotlin project, with all per-target classifier artefacts.kotlin-source: emits Kotlin source plus Gradle files without building (for downstream IntelliJ / Android Studio integration).
The driver dispatches to Gradle: ./gradlew build for the default JVM target, ./gradlew bundleRelease for Android, ./gradlew linkReleaseExecutable<TGT> for K/Native, ./gradlew jsBrowserProductionWebpack for Kotlin/JS, ./gradlew wasmJsBrowserDistribution for Kotlin/Wasm.
3. Name mangling
Mochi names to Kotlin names:
- Module path
mochilang/aiops/Pipelineto Kotlin packagemochi.user.aiops.pipeline(lowercase per Kotlin package convention; configurable via--kotlin-package-prefix, defaultmochi.user). - Mochi function
process_batchto KotlinprocessBatch(lowerCamelCase). - Mochi type
User_Recordto KotlinUserRecord(PascalCase). - Mochi sum variant
OKto KotlinOk(PascalCase data class or data object). - Mochi reserved-word collisions:
class,interface,object,fun,val,var,package,import,return,if,else,when,for,while,do,try,catch,finally,throw,null,true,false,this,super,Nothing,Any,Unitare escaped with backticks (`class`) or a_suffix.
4. Type lowering
Per 06-type-lowering:
inttoLong(KotlinIntis 32-bit; we pickLongfor Mochiintto match the 64-bit semantic of vm3).floattoDouble(IEEE 754 64-bit).booltoBoolean.stringtoString(UTF-16 internally on JVM/Android/Native/JS/Wasm, with UTF-8 conversion at I/O boundaries).bytestoByteArrayin core,kotlinx.io.bytestring.ByteStringat I/O boundaries.list<T>tokotlin.collections.List<T>(read-only) backed byArrayList.map<K, V>toMutableMap<K, V>backed byLinkedHashMap(insertion-ordered, the Kotlin / JVM default).set<T>toMutableSet<T>backed byLinkedHashSet(insertion-ordered).ordered_map<K, V>toLinkedHashMap<K, V>(already insertion-ordered) orPersistentMap<K, V>fromkotlinx.collections.immutablewhen immutability is requested.record { ... }todata class ...(val ...). Kotlin data classes synthesiseequals,hashCode,toString,copy, andcomponentN.sum type T = A | Btosealed interface Twithdata classanddata objectvariants.sealed interface(Kotlin 1.7+) is preferred oversealed classso variants can also implement other interfaces.option<T>to Kotlin nullableT?(the idiomatic Kotlin optional, built into the type system).result<T, E>to a custom sealedMochiResult<T, E>(Kotlin's stdlibkotlin.Result<T>is invariant inT, lacks anEparameter, and was originally aninternaltype; we emit our own).fun(T) -> Rto Kotlin function type(T) -> Rorsuspend (T) -> Rwhen concurrency colouring applies.agentto a custom actor class wrappingChannel<Message>.stream<T>tokotlinx.coroutines.flow.Flow<T>. Cold flow by default; hot variants viaSharedFlow/StateFlow.timetokotlin.time.Instant(stable since Kotlin 2.1; falls back tokotlinx.datetime.Instanton pre-2.1 modules).durationtokotlin.time.Duration(stable since Kotlin 1.6).
5. Expression lowering
Per 05-codegen-design §13-§22:
- Mochi literals to Kotlin literals; Kotlin integer literals default to
Int, so the codegen emits explicitLsuffix (42L) orLong(...)calls. - Mochi index expressions to Kotlin subscripts (
list[i],map[k]). - Mochi
matchto Kotlinwhenwithispatterns andwhere-style guards via secondaryif. Exhaustiveness is verified by the Kotlin compiler whenwhenis used as an expression over a sealed hierarchy. - Mochi
if-letto Kotlinlet { ... }chained off the nullable receiver. - Mochi closure literal
fn x => x + 1to Kotlin lambda{ x: Long -> x + 1 }. - Mochi method call
obj.method(args)to Kotlin method call (extension functions for record types when methods are defined outside the record). - Mochi
withexpression to Kotlindata class.copy(field = value).
6. Closures and concurrency colouring
Per 05-codegen-design §12, §17 and 09-agent-streams:
- Mochi closures lower to Kotlin lambdas. Captures are by reference for
var, by value forval; the JVM emits a synthetic class per closure. - Suspending closures (closures that call
suspendfunctions) are markedsuspend (...) -> R. - Mochi async functions (any function transitively reachable from an
awaitor agentcall) lower tosuspend fun. Sync functions stay as plainfun. Sendability of captured state is checked by Kotlin's coroutine machinery at runtime; Mochi's sendability inference at the IR level prevents non-Sendable captures from reaching cross-coroutine boundaries.
7. Runtime library
Per 04-runtime:
MochiRuntimeKMP Gradle module (Apache-2.0, ~6000 LOC).- Re-exports
kotlinx.coroutines,kotlinx.serialization,kotlinx.collections.immutable,kotlinx.datetime,kotlinx.io. - Adds Mochi-specific: agent supervisor (
MochiRuntime.Supervisor), Datalog evaluator (MochiRuntime.Datalog), ZonedDateTime helpers,JSONValuesealed-interface ADT overkotlinx.serialization.json.JsonElement, query DSL extensions. - Targets Kotlin 2.1 minimum; published to Maven Central as
dev.mochilang:mochi-runtimewith all KMP target classifiers. - Explicit-API mode (
-Xexplicit-api=strict): every public API has an explicit visibility modifier and an explicit return type, required for KMP library ABI stability.
8. Concurrency
Per 09-agent-streams:
- Mochi agent to a custom Kotlin class. The class holds a
private val mailbox: Channel<Message>(capacityChannel.UNLIMITEDby default, matching Mochi's unbounded-mailbox semantic;bounded(N)qualifier in Mochi source lowers toChannel(N)). - The class also holds a
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default). - The mailbox loop is
scope.launch { for (msg in mailbox) when (msg) { ... } }started ininit. cast(.msg)tomailbox.trySend(msg)from a non-suspending function,mailbox.send(msg)from asuspend fun.call(.req) -> Replyto asuspend funthat constructs aCompletableDeferred<Reply>, attaches it to the message, sends, andawaits the deferred.spawn f()toscope.launch { f() }inside the enclosing CoroutineScope; top-levelspawnusesGlobalScope.launch { ... }only for the program entry.- Streams:
Flow<T>for cold streams (flow { emit(...) }builder, consume via.collect { ... }). Hot variants viaSharedFlow<T>/StateFlow<T>. - Parallel blocks:
coroutineScope { launch { ... }; launch { ... } }orwithTimeout(d) { ... }for time-bounded parallel work. - Cancellation: cooperative via
ensureActive()/yield()at every loop back-edge in long-running blocks; cancellation propagates structurally via theSupervisorJobtree.
9. Memory model
- JVM and Android: HotSpot / ART tracing GC.
- Kotlin/Native: new memory model (concurrent mark-and-sweep, default since Kotlin 1.9). The 2021-era freeze-based memory model is not supported; the transpiler refuses to emit code targeting it. Configured via the absence of
kotlin.native.binary.memoryModel=experimental(the new model is unconditionally default since Kotlin 1.9). - Kotlin/JS: JavaScript engine GC (V8, SpiderMonkey, JSC).
- Kotlin/Wasm: Wasm GC.
- No tracing GC required on the Mochi side; Kotlin's runtime handles allocation.
10. Error model
Per 06-type-lowering §7, 02-design-philosophy §11:
- Mochi
Result<T, E>to custom sealedMochiResult<T, E>withOk(value: T)andErr(error: E)variants. - Mochi throwing function
fun parse() -> AST throws ParseErrorto Kotlinfun parse(): MochiResult<AST, ParseError>(no checked exceptions; Kotlin does not have them and we do not emulate them). - Mochi
try / catch(planned) to Kotlinwhen (val r = expr()) { is MochiResult.Ok -> ...; is MochiResult.Err -> ... }. - Mochi
panicto Kotlinerror(msg)which throwsIllegalStateException.
11. Portability
Per 07-kotlin-target-portability:
- JVM: Java 17 LTS minimum (Kotlin bytecode target 17). Java 21 LTS preferred but not required.
- Android: minSdk 24 (Android 7.0 Nougat, 2016-08), targetSdk 35 (Android 15, 2024-10). AGP 8.7+, Kotlin Android Plugin 2.1+.
- Kotlin/Native: 2.1+, new memory model. Targets: iosArm64, iosSimulatorArm64, iosX64 (Rosetta only), macosArm64, macosX64, linuxArm64, linuxX64, mingwX64, watchosArm64, tvosArm64.
- Kotlin/JS: 2.1+, IR backend only. Targets:
browserandnodejs. Module system: ES modules. - Kotlin/Wasm: 2.1+, marked Beta in 2.1.20 (the Wasm GC target). Shipped as Alpha with a clear caveat.
12. Build system
Per 10-build-system:
- Gradle Kotlin DSL (
build.gradle.kts), never Groovy. settings.gradle.ktsdeclares the KMP module structure.gradle/libs.versions.tomlis the version catalog with pinned versions of Kotlin, AGP, kotlinx.coroutines, etc.- Gradle wrapper bundled;
gradle/wrapper/gradle-wrapper.propertiespinned to a specific Gradle version (8.11.1 as of MEP-50 v1). - Android Gradle Plugin (AGP): 8.7+ for AAPT2, Compose 1.7 / 1.8 alpha targeting.
- iOS framework export:
XCFrameworktask on the Kotlin KMP plugin (preferred for Swift Package Manager consumption); CocoaPods integration available via thecocoapodsblock. - Compose Multiplatform:
org.jetbrains.composeGradle plugin 1.7.3+; reserved for UI-heavy fixtures, not in core lowering.
13. Reproducibility
Per 11-testing-gates §10:
- Pinned Kotlin toolchain (Gradle Kotlin DSL
kotlin("multiplatform") version "2.1.0"). - ktfmt applied deterministically.
- No
BuildConfig.TIMESTAMPor__DATE__-style stamps in emitted code. - jarsigner / apksigner timestamping excluded from byte-equality check (timestamping stamps Unix epoch); unsigned
.jarand.aabare bit-identical. gradle/libs.versions.tomlandgradle.lockfilechecked in.
14. Hardening
Per 11-testing-gates §14:
- kotlinc with
-Werror -Xexplicit-api=strict -language-version=2.1 -api-version=2.1. - No
unsafeCast<T>()orUnsafe-style constructs in user-facing code. - Android: minimum proguard/R8 rules emitted; sandbox via Android permissions manifest.
- TLS pinning available via Ktor client
HttpsURLConnectioninterceptor; manifest-driven.
15. Diagnostics
- Mochi emits Kotlin source with
@Suppressannotations only where absolutely necessary; tracked inSUPPRESSIONS.md. - Source maps:
.mochi.mapsidecar per generated.ktfor debugger integration. - JVM debug info via
-Xdebugand-Xemit-jvm-type-annotations.
16. FFI
Per 01-language-surface §10, 06-type-lowering §16:
- JVM: Mochi
extern fun foo(...) -> ...from a Java module to a Kotlinexternal fun foo(...): ...plus a JNI binding viaSystem.loadLibrary. - Kotlin/Native: Mochi
extern fun foo(...) -> ...from a C module to a Kotlin/Nativecinteropdeclaration generated via the KMP plugin'scinteropsblock (.deffile emitted per Mochiimport "c/<library>"). - Kotlin/JS: Mochi
extern fun foo(...) -> ...to a Kotlin/JSexternal fun foo(...)plus a@JsModuleannotation. - Mochi-side exports: Kotlin
@JvmStaticfor JVM,@CName("mochi_foo")for K/Native,@JsExportfor K/JS.
17. Output style
- 4-space indentation (Kotlin convention).
- K&R brace style.
- Explicit
publicmodifiers on all public APIs (required by-Xexplicit-api=strict). - Explicit type annotations on public function signatures (Library Evolution requirement).
- Sorted imports, sorted map keys in literals (for determinism).
- Trailing commas on multi-line argument and parameter lists (Kotlin 1.4+ feature).
Rationale
The five load-bearing decisions (§Abstract) flow from a single observation: Android requires Kotlin, and Kotlin Multiplatform plus the K2 compiler now make a single Kotlin codebase reach every modern client and server platform. Lower Mochi to Kotlin 2.1 and the two type systems align; the Kotlin compiler becomes a second-line check on Mochi's correctness.
The choice of Channel + CoroutineScope over the deprecated actor { } builder is forced by the kotlinx.coroutines maintainers' own ROADMAP: the builder has been pending replacement since 2017, and modern code uses the custom-class shape that we emit.
The choice of all KMP targets (not just JVM and Android) follows from the user-facing goal: Mochi should let a single team ship the same backend logic to Android, iOS, web, server, and desktop from one source. JVM-only would re-introduce the multi-language stack KMP was designed to eliminate, and MEP-47 already covers the JVM-bytecode story.
The choice of KotlinPoet-shaped emission (Square's canonical Kotlin-source emitter) over raw string templates is forced by codegen reproducibility: hand-rolled string templates drift, ktfmt alone cannot fix structural bugs. KotlinPoet gives us correctness with a familiar Gradle-side dependency.
The thin runtime (MochiRuntime) is the same philosophy that drove MEP-47 (thin dev.mochi.runtime), MEP-48 (thin Mochi.Runtime), and MEP-49 (thin MochiRuntime SwiftPM package): the platform's first-party libraries are the canonical surface; Mochi adds only what is missing.
Backwards Compatibility
MEP-50 is purely additive. mochi run keeps the vm3 path; existing transpiler3 targets (C, BEAM, JVM, .NET, Swift) are untouched. No Mochi language surface changes. The Kotlin target lands as a new transpiler3/kotlin/ subtree and new tests/transpiler3/kotlin/ fixture corpus. Phase gates ensure no cross-target regression. On the JVM, MEP-47 (direct bytecode) and MEP-50 (kotlin-jvm via kotlinc) coexist; users pick per workload.
Reference Implementation
Implementation lives under:
transpiler3/kotlin/aotir/: MochiaotirIR consumer (target-agnostic).transpiler3/kotlin/lower/:aotirto Kotlin source codegen pass.transpiler3/kotlin/emit/: file writer,build.gradle.kts/settings.gradle.kts/libs.versions.tomlemitters, KotlinPoet invocation, ktfmt invocation.transpiler3/kotlin/build/: driver for./gradlew build,./gradlew bundleRelease,./gradlew linkReleaseExecutable<TGT>, bundletool validate.runtime/kotlin/MochiRuntime/: KMP Gradle module source.tests/transpiler3/kotlin/: fixture corpus and gate tests.
The transpiler3/kotlin codegen pass is approximately 4000 LOC of Go (a Mochi-side syntax tree model plus KotlinPoet driver). The runtime library MochiRuntime is approximately 6000 LOC of Kotlin across ~30 files. The fixture corpus targets ~400 fixtures by Phase 18 completion.
Phase plan
Eighteen phases mirroring MEP-45 / MEP-46 / MEP-47 / MEP-48 / MEP-49:
| Phase | Name | Surface |
|---|---|---|
| 1 | Hello world | basic print, let, int |
| 2 | Scalars | int/float/bool/string ops, comparisons |
| 3.1 | Lists | list literal, index, len, for-each |
| 3.2 | Maps | map literal, index, len, keys, values, has |
| 3.3 | Sets | set literal, add, has, len |
| 3.4 | Lists of records | list[record], comprehensions over records |
| 4 | Records | records, methods, equality, copy |
| 5 | Sum types | sum types, pattern matching, exhaustiveness |
| 6 | Closures and higher-order | closures, higher-order, suspend colouring |
| 7 | Query DSL | from/where/select, group_by, order_by, joins |
| 8 | Datalog | facts, rules, recursion |
| 9 | Agents | actor pattern, spawn, call, cast |
| 10 | Streams | Flow, AsyncSequence, await foreach |
| 11 | Async colouring | suspend colouring, structured concurrency |
| 12 | FFI | JNI (JVM), cinterop (K/Native), external (K/JS) |
| 13 | LLM | generate (provider-pluggable) |
| 14 | Fetch | fetch (Ktor client, against local test server) |
| 15 | Android App Bundle | .aab/.apk bundle, signing, AGP 8.7+ build |
| 16 | Reproducible build | byte-identical .jar and .aab |
| 17 | Kotlin/Native single binaries | K/Native single binary, Linux/macOS/Windows |
| 18 | Play Console validation | Google Play Console pre-launch validation |
Per 11-testing-gates §18, a phase lands only when all gates are green: per-phase fixture corpus, kotlinc clean, ktfmt fixed-point, cross-target differential where applicable, Android App Bundle validation for Phase 15+, K/Native single-binary gate for Phase 17, Play Console pre-launch validation for Phase 18.
Dependencies
- MEP-4 (Type System), MEP-5 (Type Inference), MEP-13 (ADTs and Match): Mochi front-end.
- MEP-45 (C transpiler):
aotirIR plus shared passes. - MEP-46 (BEAM transpiler): shared IR confirmation, cross-target gates.
- MEP-47 (JVM transpiler): shared IR confirmation; MEP-47 emits JVM bytecode directly (skip kotlinc), MEP-50 emits Kotlin source then routes through kotlinc. Both produce JVM
.jarartefacts; the cross-target differential gate verifies byte-equal stdout. - MEP-48 (.NET transpiler): shared IR confirmation, prior art for C#-source emission via Roslyn
SyntaxFactory. - MEP-49 (Swift transpiler): shared IR confirmation, prior art for Swift-source emission via a Mochi-side syntax tree.
External:
- Kotlin toolchain 2.1+ (JetBrains-shipped via Gradle Kotlin plugin).
- Gradle 8.11+ (bundled via the Gradle wrapper).
- Android Gradle Plugin 8.7+ (for
kotlin-androidtarget). - Android SDK platform-35, build-tools-35.0.0, NDK r26+ (for
kotlin-androidtarget). - KotlinPoet 1.18+ (Square's Kotlin-source emitter, runtime dep of the transpiler).
- ktfmt 0.49+ (Facebook's deterministic Kotlin formatter, optional dep of the transpiler).
- bundletool (Google, for
.aabvalidation). - apksigner, jarsigner, zipalign (Android SDK build-tools).
- kotlinx.coroutines 1.10+, kotlinx.serialization 1.7+, kotlinx.collections.immutable 0.3.8+, kotlinx.datetime 0.6+ (runtime deps).
- Ktor 3.0+ (client, for
fetch).
Open questions
Per 12-risks-and-alternatives §3:
- Q1: Jetpack Compose Multiplatform lowering. v2.
- Q2: SwiftUI interop from Kotlin/Native iOS frameworks. v2.
- Q3: Embedded Kotlin / Kotlin/Native stripped-down runtime. v2.
- Q4: Kotlin/Wasm WASI target (alongside Wasm GC). v2 when toolchain matures.
- Q5: Kotlin/Native watchOS / tvOS coverage. v2.
- Q6: Distributed agents over kotlinx.coroutines
Channel<T>networking. v2. - Q7: Google Play Console release automation via Gradle Play Publisher plugin. v2.
Security considerations
- Android: apksigner v3 / v4 mandatory for Play Store distribution; minimum target API 31 for new apps (Google Play 2024 policy, MEP-50 targets API 35 to stay current).
- Android manifest: deny-by-default permissions; runtime permission prompts emitted via Mochi's permission DSL.
- JVM: jarsigner with SHA-256 mandatory; reproducible build excludes signature.
- TLS pinning available via Ktor client
HttpsURLConnectioninterceptor. - FFI requires manifest entry; reflection-bypass via
kotlin-reflectrejected at runtime (kotlin-reflectis not a runtime dep ofMochiRuntime). - K/Native: no
Unsafeconstructs in user-facing code; runtime libraryMochiRuntime.Unsafenamespace contains pointer manipulations under@OptIn(ExperimentalForeignApi::class). - Kotlin/JS and Kotlin/Wasm: emitted code has no
evalorFunctionconstructor usage; CSP-strict by default. - Play Console pre-launch validation runs every fixture through Google's automated security scanner; failures block release.
References
- Kotlin 2.0.0 release notes. https://kotlinlang.org/docs/whatsnew20.html
- Kotlin 2.1.0 release notes. https://kotlinlang.org/docs/whatsnew21.html
- Kotlin 2.1.20 release notes. https://kotlinlang.org/docs/whatsnew2120.html
- K2 compiler reference. https://kotlinlang.org/docs/k2-compiler-migration-guide.html
- Kotlin Multiplatform overview. https://kotlinlang.org/docs/multiplatform.html
- Kotlin Multiplatform stable announcement (2023-11). https://blog.jetbrains.com/kotlin/2023/11/kotlin-multiplatform-stable/
- kotlinx.coroutines. https://github.com/Kotlin/kotlinx.coroutines
- kotlinx.coroutines actor builder deprecation (issue 87). https://github.com/Kotlin/kotlinx.coroutines/issues/87
- kotlinx.serialization. https://github.com/Kotlin/kotlinx.serialization
- kotlinx.collections.immutable. https://github.com/Kotlin/kotlinx.collections.immutable
- kotlinx.datetime. https://github.com/Kotlin/kotlinx-datetime
- Kotlin/Native memory model. https://kotlinlang.org/docs/native-memory-manager.html
- Kotlin/JS IR backend. https://kotlinlang.org/docs/js-ir-compiler.html
- Kotlin/Wasm. https://kotlinlang.org/docs/wasm-overview.html
- Android Gradle Plugin 8.7 release notes. https://developer.android.com/build/releases/gradle-plugin
- Android App Bundle. https://developer.android.com/guide/app-bundle
- bundletool. https://github.com/google/bundletool
- KotlinPoet. https://square.github.io/kotlinpoet/
- ktfmt. https://github.com/facebook/ktfmt
- ktlint. https://github.com/pinterest/ktlint
- Jetpack Compose. https://developer.android.com/jetpack/compose
- Compose Multiplatform. https://www.jetbrains.com/lp/compose-multiplatform/
- Ktor. https://ktor.io
- Spring Boot Kotlin support. https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.kotlin
- Google Play Console pre-launch validation. https://support.google.com/googleplay/android-developer/answer/9866151
- Gradle 8.11. https://docs.gradle.org/8.11/release-notes.html
Research notes
Twelve research notes elaborate the design:
- 01-language-surface: Mochi sub-languages mapped onto Kotlin 2.1 lowering obligations.
- 02-design-philosophy: Why Kotlin, why 2.1 floor, why Channel + CoroutineScope (not the deprecated
actor { }), why all KMP targets. - 03-prior-art-transpilers: J2K (IntelliJ Java-to-Kotlin), ts2kt (TypeScript-to-Kotlin definition files), KotlinPoet (Square's source emitter), the Compose UI compiler, the J2CL story.
- 04-runtime: kotlin stdlib, kotlinx.coroutines, kotlinx.serialization, kotlinx.collections.immutable, kotlinx.datetime, MochiRuntime layout.
- 05-codegen-design: Mochi-side syntax tree emission via KotlinPoet, ktfmt integration, aotir IR reuse.
- 06-type-lowering: Type-by-type mapping to Kotlin 2.1 (Long, data class, sealed interface, custom actor class, Flow, kotlinx.serialization Codable).
- 07-kotlin-target-portability: KMP target matrix (JVM, Android, K/Native, K/JS, K/Wasm), Static Linux SDK comparison, Play Console packaging.
- 08-dataset-pipeline: Query DSL lowering via Kotlin
SequenceplusFlowpluskotlinx.collections.immutable, Datalog engine. - 09-agent-streams: Mochi agents as a custom actor class wrapping
Channel<Message>, Flow streams, structured concurrency viaCoroutineScope(SupervisorJob() + Dispatchers.Default). - 10-build-system: Gradle Kotlin DSL, KGP, AGP, Maven Central publish, version catalog, Gradle wrapper.
- 11-testing-gates: Per-phase Go test gates, Kotlin version matrix, Play Console validation, K/Native single-binary gate.
- 12-risks-and-alternatives: Risk register, Java-source emission and Scala interop and Groovy DSL rejected and why.
The 18-phase delivery plan walks from hello-world through scalars, collections, records, sums, closures, queries, datalog, agents, streams, async, FFI, LLM, fetch, then Android App Bundle, reproducibility, Kotlin/Native single binaries, and Play Console validation. Each phase is gated against vm3.