May 2026 (v0.14.0)
v0.14.0 completes the Mochi-to-JVM transpiler. Every phase of MEP-47 is now landed and the MEP is marked Final. You can compile a Mochi source file to a runnable fat jar, a custom JRE image, a GraalVM native binary, or an OS-native installer, without writing a single line of Java or Maven configuration.
mochi build --target=jvm-uberjar hello.mochi -o hello.jar
java -jar hello.jar
The pipeline is entirely in-process: parser.Parse -> types.Check
-> aotir.Lower -> jvmlower.Lower -> javasrc.CompilationUnit ->
javax.tools.JavaCompiler -> .class files -> uberjar. No mvn, no
gradle, no javac on $PATH required for the default uberjar
target (the in-process javax.tools instance ships with the JDK you
point at via JAVA_HOME).
1. JVM transpiler: MEP-47 Final
MEP-47 covers 19 phases from hello-world through scalars, collections, records, sum types, closures, queries, Datalog, Loom agents, streams, async/await, JDK FFI, LLM generation, HTTP fetch, release packaging, GraalVM native-image, CI matrix with reproducibility, and Maven Central publication. All 19 phases are now LANDED.
1.1 Pipeline and packaging (Phases 0-1)
The transpiler pipeline lives under transpiler3/jvm/. The entry
point is transpiler3/jvm/build/Driver.Build, which accepts a
DriverConfig struct specifying the target (uberjar, jlink, jpackage,
native-image, source), output paths, and JDK root.
The pipeline stages are:
parser.Parse-- identical to every other Mochi backend.types.Check-- shared type-checker; JVM backend adds no new types here.aotir.Lower-- the shared MEP-45/MEP-46 IR lowering, which runs monomorphisation, closure-conversion, and match-to-decision-tree.jvmlower.Lower-- JVM-specific lowering: convertsaotirnodes tojavasrc.CompilationUnittrees (a structural Java-source AST).javasrc.Emit-- rendersCompilationUnitto Java source text.javax.tools.JavaCompiler-- in-process javac compiles the source to.classfiles.build.PackUberJar-- collects all.classfiles plus themochi-runtime.jarinto a zip archive with a validMANIFEST.MF(Main-Class: dev.mochi.user.Main).
The emitted Java package for user code is dev.mochi.user. The
runtime library is dev.mochi.runtime.*. A Main.java wrapper is
emitted automatically; it calls the Mochi main function entry point.
BLAKE3 content-addressed cache. Unchanged source files are served
from .mochi/cache/jvm/ with a copy-only no-op. The cache key is
BLAKE3 over file contents concatenated with a compilerVersion
sentinel. The JVM cache is a parallel slot to the BEAM and C caches;
all three coexist under .mochi/cache/.
1.2 Scalars and control flow (Phases 2-3)
Integers lower to Java long. Overflow wraps at 64-bit boundaries
(same as vm3). Divide-by-zero raises MochiPanic(MOCHI_ERR_DIVZERO).
Floats lower to Java double. IEEE 754 NaN/Inf propagation is
preserved: 0.0 / 0.0 produces NaN, 1.0 / 0.0 produces
Infinity. The Double.NaN and Double.POSITIVE_INFINITY sentinels
are used directly in emitted Java.
Booleans lower to Java boolean. && and || lower to Java
short-circuit &&/|| rather than &/|, preserving Mochi's
short-circuit semantics.
Strings lower to Java String (UTF-16 internally, UTF-8 I/O).
String concatenation uses +; the javac backend inlines to
StringBuilder.append calls in the generated bytecode.
Control flow. if/else lowers to Java if/else. while
lowers to Java while. for x in range(lo, hi) lowers to Java
for (long x = lo; x < hi; x++). for x in collection lowers to
Java enhanced for-each. break and continue lower directly. return
lowers to Java return.
Print. print(x) lowers to System.out.println(str(x)) where
str is the Mochi stringification builtin, itself lowered to
MochiRuntime.str(x).
1.3 Collections (Phase 4)
list<T> lowers to java.util.ArrayList<T>. List literals
[1, 2, 3] lower to new ArrayList<>(Arrays.asList(1L, 2L, 3L)).
len(xs) lowers to xs.size(). append(xs, x) lowers to a copy-
append that returns a new ArrayList (value semantics). xs[i] lowers
to xs.get((int) i) with a checked cast for bounds. for x in xs
lowers to enhanced for-each.
map<K,V> lowers to java.util.LinkedHashMap<K,V> (insertion-
order preserved, matching vm3 semantics). Map literals {"a": 1}
lower to a new LinkedHashMap<>() followed by put calls. m[k]
lowers to MochiRuntime.mapGet(m, k), which throws
MochiPanic(MOCHI_ERR_KEY_NOT_FOUND) on missing key. m[k] = v
lowers to MochiRuntime.mapSet(m, k, v) returning a new copy.
has(m, k) lowers to m.containsKey(k).
set<T> lowers to java.util.LinkedHashSet<T>. Set literals
{1, 2, 3} lower to new LinkedHashSet<>(Arrays.asList(...)).
add(s, v) returns a new LinkedHashSet with v appended.
has(s, v) lowers to s.contains(v). len(s) lowers to s.size().
1.4 Records (Phase 5)
Records lower to Java record classes (JEP 395, GA in JDK 16+).
A Mochi record type Point { x: int, y: int } emits:
record MochiPoint(long x, long y) {}
Field access p.x lowers to p.x() (the canonical accessor).
Record update {...p, x: 10} lowers to a new MochiPoint(10, p.y())
constructor call, which javac compiles to a simple stack-push sequence.
Records are immutable by construction (Java record fields are
final). The Mochi type checker guarantees structural equality
semantics; Java record provides equals/hashCode/toString via
RecordType.RecordComponents automatically.
1.5 Sum types and pattern matching (Phase 6)
Sum types lower to Java sealed interfaces (JEP 409, GA in JDK 17+)
with record implementations for each variant.
type Shape = Circle(r: float) | Rect(w: float, h: float)
emits:
sealed interface MochiShape permits MochiShape.Circle, MochiShape.Rect {
record Circle(double r) implements MochiShape {}
record Rect(double w, double h) implements MochiShape {}
}
Pattern matching. match x { arm => expr, ... } lowers to Java
switch pattern expressions (JEP 441, GA in JDK 21):
switch (x) {
case MochiShape.Circle c -> ...;
case MochiShape.Rect r -> ...;
}
The sealed interface + switch combination is exhaustiveness-checked by javac, giving a second correctness gate on top of the Mochi type checker.
option[T] lowers to MochiOption<T> (sealed interface with
Some<T> and None records). option.get(x) is MochiRuntime.optGet(x).
Result[T,E] lowers to MochiResult<T,E> (sealed interface with
Ok<T> and Err<E> records).
1.6 Closures and higher-order functions (Phase 7)
Closures. Anonymous functions lower to Java lambdas. Mochi's
function type fun(int, int) -> int lowers to MochiFn2<Long, Long, Long> (from the runtime's typed functional-interface zoo). The lambda
body captures free variables via Java's standard closure mechanism.
Named function references. &f (a first-class reference to named
function f) lowers to a method reference ClassName::f.
Partial application. add(5, _) lowers to a lambda that captures
5L in the first argument position and passes its own argument in the
second: (b) -> add(5L, b). The _ placeholder is detected during
jvmlower.Lower; lowerPartialApply synthesises the capturing lambda.
Higher-order builtins. map(xs, f) lowers to a Java stream:
xs.stream().map(f::apply).collect(Collectors.toCollection(ArrayList::new)).
filter(xs, pred) uses stream().filter. reduce(xs, init, f) uses
stream().reduce. The stream path is chosen over a manual loop because
javac + C2 JIT can vectorise and parallelise the stream operations
on warm code paths.
1.7 Query DSL (Phase 8)
from x in xs where pred select proj lowers to a Java stream pipeline:
xs.stream()
.filter(x -> pred)
.map(x -> proj)
.collect(Collectors.toCollection(ArrayList::new))
Aggregations: count(q) appends .count(), sum(q, f) appends
mapToLong(f).sum(), avg(q, f) appends mapToDouble(f).average().
Group-by: group x by key select agg lowers to
Collectors.groupingBy(x -> key, downstream).
Hash join: from x in xs join y in ys on x.id == y.id select ...
lowers to a two-pass approach: first builds a HashMap keyed by the
join column from the left side, then streams the right side doing
lookups in O(1) per probe.
Sort, take, skip: .sorted(Comparator.comparingLong(x -> key)),
.limit(n), .skip(n).
1.8 Datalog (Phase 9)
Facts, rules, and recursive queries compile to MochiDatalog, a
semi-naive fixpoint evaluator in the runtime library. Each relation is
backed by a LinkedHashSet for membership and a delta ArrayList for
incremental updates per round.
Recursive rules run until the delta is empty (fixpoint). Negation-as- failure is supported when the negation is stratifiable: the type checker computes the dependency graph and rejects cycles through negation.
Multi-predicate rules and multi-free-variable queries are supported.
Datalog literals in Mochi source lower to MochiDatalog.Relation
instances; rule bodies lower to MochiDatalog.Rule objects with a
Java lambda head and a list of body predicates.
1.9 Agents (Loom virtual threads) (Phase 10)
Agent declarations lower to a Java class implementing the agent
loop. Each agent type emits a class with a LinkedTransferQueue<Object>
mailbox, a run() method containing the agent's message-dispatch loop,
and a static start(args...) factory method that calls
Thread.ofVirtual().start(loop).
public class MochiCounter {
private final LinkedTransferQueue<Object> mailbox = new LinkedTransferQueue<>();
public static MochiCounter start(long initial) {
var agent = new MochiCounter(initial);
Thread.ofVirtual().start(agent::run);
return agent;
}
private void run() {
long count = initial;
while (true) {
Object msg = mailbox.take(); // parks virtual thread, yields carrier
// dispatch on msg type ...
}
}
}
spawn AgentType(args...) lowers to MochiCounter.start(args...).
The returned reference is a plain Java object reference; message sends
are direct method calls via a send(msg) method that delegates to
mailbox.put(msg).
on close { ... } blocks lower to a finally block in run(),
so cleanup code always runs whether the agent exits normally or via
an exception.
Virtual thread overhead. Each virtual thread starts with ~200 bytes
of heap and a minimal initial stack. The JDK 24 fix for synchronized
pinning (JEP 491) means agents can safely use synchronized methods in
dependencies without pinning carrier threads. At 100K concurrent agents
the RSS footprint is under 100 MB.
1.10 Streams (Phase 11)
stream<T> lowers to MochiStream<T>, which wraps a
java.util.concurrent.Flow.Publisher<T> (JDK 9 Reactive Streams API)
with a SubmissionPublisher<T> implementation for backpressure.
publish(stream, value) calls SubmissionPublisher.submit(value).
The submit method blocks when all subscriber buffers are full,
providing automatic backpressure.
subscribe(stream, callback) calls publisher.subscribe(subscriber)
with a Flow.Subscriber<T> whose onNext(item) calls callback.
subscribe_limit(stream, N) uses a bounded subscriber that drops
messages when its buffer exceeds N items. MochiStream.subscribeLimit
implements the bounded subscriber using Flow.Subscription.request(1)
to control demand one item at a time.
Channels. chan<T> lowers to MochiChan<T>, backed by an
ArrayBlockingQueue<T> of user-specified capacity.
1.11 async/await (Phase 12)
async { ... } lowers to CompletableFuture.supplyAsync(() -> body, vtExecutor) where vtExecutor is
Executors.newVirtualThreadPerTaskExecutor(). Each async task runs on
a fresh virtual thread from the JDK 21 virtual-thread pool.
await expr lowers to MochiRuntime.await(future), which calls
CompletableFuture.get() on the carrier virtual thread. Because
virtual threads park instead of blocking a carrier, thousands of
concurrent await calls do not starve the carrier pool.
await_all(futures) lowers to
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
followed by a stream().map(CompletableFuture::join) to collect
results in order.
let f1 = async { fetch "https://example.com/a" }
let f2 = async { fetch "https://example.com/b" }
let results = await_all([f1, f2])
1.12 JDK FFI (Phase 13)
extern java fun declarations import any method from the JDK
or any Maven Central library on the classpath:
extern java fun java.time.Instant.now(): string
extern java fun java.util.UUID.randomUUID(): string
The import splits on the last dot: java.time.Instant is the class,
now is the method name. Static methods lower to ClassName.method(args).
Instance methods lower to args[0].method(args[1:]).
Return type mapping. The Mochi return type annotation drives
wrapping: string appends .toString(); int emits (long) result;
bool emits (boolean) result. Complex types use MochiRuntime.coerce.
import "dev.mochi:some-library:1.2.3" adds a Maven Central
dependency to the build. The runtime resolver downloads the jar to
.mochi/cache/jvm/deps/ via java.net.http.HttpClient and adds it to
the javax.tools compilation classpath.
1.13 LLM generation (Phase 14)
Cassette playback. generate provider { prompt: "..." } lowers to
MochiLLM.generate(provider, model, prompt). In cassette mode
(MOCHI_LLM_CASSETTE_DIR set), responses are looked up from
pre-recorded files keyed by a DJB2 hash of "provider\0model\0prompt".
This makes LLM-using programs deterministic and credential-free in CI.
Live providers. When MOCHI_LLM_CASSETTE_DIR is not set:
OPENAI_API_KEYset: POST toapi.openai.com/v1/chat/completionsviajava.net.http.HttpClientwith HTTP/2. Default modelgpt-4o-mini. JSON parsed via JacksonObjectMapper.ANTHROPIC_API_KEYset: POST toapi.anthropic.com/v1/messageswithanthropic-version: 2023-06-01. Default modelclaude-haiku-4-5-20251001.- Neither set: warning to stderr, returns empty string.
Structured output. generate provider { prompt: "...", schema: s }
appends "\nRespond with JSON matching this schema: <schema>" to the
prompt before cassette lookup. The schema expression evaluates to a
Mochi record type at compile time; MochiRuntime.schemaOf(T.class)
generates the JSON Schema descriptor.
1.14 HTTP fetch (Phase 15)
fetch URL into var lowers to MochiFetch.get(url). The runtime
method uses java.net.http.HttpClient (JDK 11+) with:
- HTTP/2 preferred, HTTP/1.1 fallback.
- TLS: system default
SSLContext(JDK trust store; no custom CA bundle needed on modern JDKs with updated roots). - Timeout: 30 seconds connect + read (configurable via
MOCHI_HTTP_TIMEOUT_SECONDS). - Response body returned as
Stringdecoded with UTF-8. - Non-200 responses throw
MochiPanic(MOCHI_ERR_HTTP, status).
json_decode(s) lowers to MochiJson.decode(s). The runtime uses
Jackson ObjectMapper to parse the JSON string and returns a
LinkedHashMap<String, Object> coerced to Mochi's map<string,string>
(leaf values stringified).
1.15 Release packaging (Phase 16)
Five packaging targets are available:
| Target flag | Output | Cold-start target | Self-contained? |
|---|---|---|---|
--target=jvm-uberjar | Single fat .jar (default) | <= 500 ms | No (needs JDK) |
--target=jlink | Custom JRE image directory | <= 200 ms | Yes (bundles JDK modules) |
--target=jpackage | OS installer (.dmg/.deb/.msi) | <= 200 ms | Yes (bundles custom JRE) |
--target=jvm-native | GraalVM native binary | <= 50 ms | Yes (no JDK needed) |
--target=jvm-source | Java source files for inspection | -- | -- |
Uberjar (PackUberJar): Extracts all entries from the runtime jar,
collects all .class files from user compilation, sorts entries
lexicographically, writes with a fixed timestamp from
SOURCE_DATE_EPOCH, and prepends the META-INF/MANIFEST.MF with
Main-Class: dev.mochi.user.Main. The resulting .jar is runnable
with java -jar.
jlink: Calls jlink --add-modules <detected-modules> --output outDir.
Module detection runs jdeps --list-deps on the uberjar to find the
minimal module set. The result is a self-contained directory (~50 MB)
with a bundled JRE and a bin/java launcher.
jpackage: Calls jpackage --type <platform-type> --app-image <jlink-output> --input . --main-jar hello.jar --dest outDir. Platform
types: dmg on macOS, deb on Linux, msi on Windows.
Java source: Writes all emitted .java files to an output
directory for inspection, debugging, or manual compilation.
1.16 GraalVM native-image (Phase 17)
BuildNativeImage(outJar, outExe) calls the native-image tool
(from GraalVM or Liberica NIK) with:
native-image -jar outJar -H:Name=<base> -H:Path=<dir>
--no-fallback --initialize-at-build-time
-H:+ReportExceptionStackTraces
The -jar flag reads the main class from MANIFEST.MF. --main-class
is not passed (it is invalid with -jar and causes a build error).
--no-fallback prevents native-image from producing a fallback image
that silently requires a JVM. --initialize-at-build-time initialises
all classes at build time for maximum startup performance.
Reachability metadata. The mochi-runtime.jar ships a
META-INF/native-image/ directory with pre-generated reachability
metadata covering reflection, proxy, and resource configs. This covers
all runtime uses of reflection for JSON serialisation, virtual-thread
scheduling, and Flow publisher internals.
MeasureStartup(exe) runs the executable and measures wall-clock
time until exit. The CI gate asserts cold start <= 50 ms on the
hello-world fixture.
Auto-skip. Tests requiring native-image call
build.FindNativeImage() and skip with t.Skip("native-image not found") when GraalVM is absent. The blocking CI job installs
graalvm/setup-graalvm@v1 with Liberica NIK 25.
1.17 CI matrix and reproducibility (Phase 18)
CI matrix (jvm.yml): JDK 21 and JDK 25 (Temurin) on:
ubuntu-24.04(x86-64 Linux)ubuntu-24.04-arm(arm64 Linux)macos-14(Apple Silicon macOS)windows-2022(x86-64 Windows)
Windows matrix cells use sparse checkout to avoid tests/rosetta/
(which contains filenames with trailing ... that are invalid on
Windows NTFS). The native-image job runs on ubuntu-24.04 with
Liberica NIK 25. JDK 26 EA runs nightly as a non-blocking smoke test.
Reproducible uberjars. Uberjar builds are bit-identical when
SOURCE_DATE_EPOCH is fixed. The CI sets SOURCE_DATE_EPOCH=1700000000.
The implementation:
- Collects all jar entries into a
[]JarEntryslice. - Sorts lexicographically by path (
sort.Slice). - Writes each entry with
w.CreateHeader(&zip.FileHeader{Modified: ts})using the fixedtsfromSOURCE_DATE_EPOCH.
TestPhase17Reproducible builds the same fixture twice in separate
temp directories with separate Driver instances, then opens both jars
and compares SHA-256 of every entry byte-for-byte. Both builds must be
identical.
Implementation-Version in the jar manifest is set to 0.14.0
matching the pom.xml artifact version.
1.18 Maven Central (Phase 19)
dev.mochi:mochi-runtime:0.14.0 is published to Maven Central
via the Central Publisher Portal (central.sonatype.com). Users can
add a standard Maven dependency:
<dependency>
<groupId>dev.mochi</groupId>
<artifactId>mochi-runtime</artifactId>
<version>0.14.0</version>
</dependency>
or Gradle:
implementation("dev.mochi:mochi-runtime:0.14.0")
Artifact bundle includes:
mochi-runtime-0.14.0.jar-- runtime classesmochi-runtime-0.14.0-sources.jar-- all.javasource filesmochi-runtime-0.14.0-javadoc.jar-- Javadoc for all public APIsmochi-runtime-0.14.0.pom-- POM with required Central metadata.ascsignatures for each artifact (viamaven-gpg-plugin 3.2.4)
Publish workflow (.github/workflows/jvm-publish.yml): triggers
on v0.* tags and nightly. Uses central-publishing-maven-plugin 0.5.0
with mvn deploy. The MAVEN_CENTRAL_TOKEN, GPG_PRIVATE_KEY, and
GPG_PASSPHRASE CI secrets must be set in the repository settings.
TestPhase18Publish: resolves mochi-runtime-0.14.0.jar from the
local build target directory, compiles a minimal Java class against it,
runs it, and verifies stdout. Skipped in -short mode.
2. Test corpus
The JVM transpiler test suite lives in transpiler3/jvm/build/ and
covers all 19 phases:
| Test function | Phase | Coverage |
|---|---|---|
TestPhase0Skeleton | 0 | Driver.Build round-trip; toolchain detection |
TestPhase1Hello | 1 | Hello-world fixture; stdout byte-equal to vm3 |
TestPhase2Scalars | 2 | int/float/bool/string; NaN/Inf; divzero |
TestPhase2ControlFlow | 2 | if/while/for; break/continue; nested loops |
TestPhase3Lists | 3 | list literals; append/len/index; for-in |
TestPhase3Maps | 3 | map literals; get/set/has; for-in |
TestPhase3Sets | 3 | set literals; add/has/len |
TestPhase4Records | 4 | record types; field access; record update |
TestPhase5Sums | 5 | sealed variants; match patterns; option/Result |
TestPhase6Closures | 6 | lambdas; partial apply; higher-order builtins |
TestPhase7Query | 7 | from/where/select; group-by; hash join; sort/take/skip |
TestPhase8Datalog | 8 | facts; recursive rules; negation-as-failure |
TestPhase9Agents | 9 | spawn; mailbox; on close; 100K agent RSS |
TestPhase10Streams | 10 | publish/subscribe; backpressure; channels |
TestPhase11Async | 11 | async/await; await_all; error propagation |
TestPhase12FFI | 12 | extern java fun; static + instance methods |
TestPhase13LLM | 13 | cassette playback; structured output |
TestPhase14Fetch | 14 | HTTP GET; json_decode |
TestPhase15Packaging | 15 | uberjar; jlink; jpackage; jvm-source |
TestPhase16NativeImage | 16 | native-image build; cold-start <= 50 ms |
TestPhase17Reproducible | 17 | two builds; SHA-256 byte-equal per entry |
TestPhase17Matrix | 17 | hello.mochi on current JDK; stdout check |
TestPhase18Publish | 18 | local jar compile; run; stdout check |
BenchmarkIntLoop | 17 | warm throughput vs vm3 baseline |
BenchmarkAgentMemory | 10 | RSS at 100K agents <= 100 MB |
BenchmarkColdStart | 16 | cold start: uberjar <= 500 ms; native <= 50 ms |
All tests are green on JDK 21 and JDK 25 on Linux x86-64, Linux arm64,
macOS arm64, and Windows x86-64. Native-image tests auto-skip on
standard JDK installs and run under Liberica NIK 25 in the native-image
CI job.
3. New compiler pipeline files
transpiler3/jvm/ directory tree:
transpiler3/jvm/
build/
driver.go -- Driver.Build; target dispatch
uberjar.go -- PackUberJar; reproducible zip packing
native.go -- BuildNativeImage; MeasureStartup
toolchain.go -- FindJavac, FindJava, FindNativeImage, FindJlink
cache.go -- BLAKE3 content-addressed cache
phase{00-18}_test.go
bench_test.go
lower/
lower.go -- aotir -> javasrc CompilationUnit
lower_expr.go -- expression lowering
lower_stmt.go -- statement lowering
lower_type.go -- type-to-Java-type mapping
javasrc/
ast.go -- CompilationUnit, ClassDecl, MethodDecl, ...
emit.go -- renders AST to Java source string
runtime/
pom.xml -- dev.mochi:mochi-runtime:0.14.0 (Maven Central)
src/main/java/dev/mochi/runtime/
MochiRuntime.java -- core helpers (str, coerce, await, mapGet, ...)
MochiFetch.java -- HTTP GET via java.net.http
MochiLLM.java -- generate; cassette; OpenAI; Anthropic
MochiJson.java -- json_decode via Jackson
MochiDatalog.java -- semi-naive fixpoint evaluator
MochiStream.java -- Flow.Publisher wrapper + backpressure
MochiChan.java -- ArrayBlockingQueue channel
MochiFn{1-8}.java -- typed functional interfaces
MochiOption.java -- sealed interface Some<T> | None
MochiResult.java -- sealed interface Ok<T> | Err<E>
MochiPanic.java -- runtime exception
4. aotir IR lowering table
MEP-47 added no new IR nodes to the shared aotir package. All JVM-
specific patterns are handled in jvmlower.Lower by mapping existing
nodes to the appropriate Java construct. The IR remains shared across
the C, BEAM, and JVM backends.
| aotir node | Java lowering |
|---|---|
AgentDecl | class with LinkedTransferQueue mailbox + Thread.ofVirtual() start |
SpawnExpr | AgentClass.start(args) |
AsyncExpr | CompletableFuture.supplyAsync(() -> body, vtExecutor) |
AwaitExpr | MochiRuntime.await(future) |
StreamDecl | MochiStream<T> wrapping SubmissionPublisher<T> |
SumTypeDecl | sealed interface + record variants |
MatchExpr | switch pattern expression (JEP 441) |
RecordDecl | Java record class |
ExternFuncDecl | static or instance method call on the named class |
GenerateExpr | MochiLLM.generate(provider, model, prompt) |
FetchExpr | MochiFetch.get(url) |
5. Compatibility
v0.14.0 is additive. All existing Mochi programs continue to run under
vm3 with mochi run. mochi build --target=c-aot from MEP-45 is
unchanged. mochi build --target=beam-escript from MEP-46 is
unchanged. mochi build --target=jvm-uberjar is the new entry point
for JVM output.
JDK 21 is the minimum supported JDK version. JDK 25 (LTS) is the recommended production JDK. JDK 17 and older are not supported.
dev.mochi:mochi-runtime:0.14.0 is Apache-2.0 licensed and compatible
with GPL/MIT/BSD downstream uses.
6. Upgrade
curl -fsSL https://get.mochi-lang.dev | sh
mochi --version # 0.14.0
Or, with Docker:
docker pull ghcr.io/mochilang/mochi:0.14.0
Or, from source:
git pull && make build
Pre-built binaries for all five tier-1 triples (linux/amd64, linux/arm64, darwin/arm64, windows/amd64, darwin/amd64) are available on the GitHub release page.