Skip to content

This release includes breaking changes for platform teams planning a safe upgrade.

Published 28d Build & Package
✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

clojure compiler go interpreter scripting-language vm

Affected surfaces

auth breaking_upgrade

Summary

AI summary

Broad release touches Performance, ir, Language and runtime, and wasm.

Full changelog

let-go v1.11.0

First release since v1.10.0 (2026-06-08). 109 commits: a browser-runtime host-decoupling arc, native-Go IR lowering across most of the Clojure-compat surface (now emitting typed cross-namespace direct calls), terminal mouse input, and a round of VM and compiler performance work. No breaking changes.

Browser runtime and client-owned shell

The WASM host now talks to Go through typed seams instead of ad-hoc JS shims, and the embedding page can own the shell.

  • -w-shell none lets the host page supply its own shell; lg-host.js is split out so a client can drive the runtime directly (#245).
  • -w-wasm external ships the payload as a separate streaming main.wasm rather than inlining it (#246).
  • Typed host seams: output through a Go HostWriter for *out*/*err* (#231), a HostEmitter for js/emit (#241), a KeySource for term/read-key and key-pending? (#244), and a host-bound storage seam (#315).
  • js/url-param reads a query parameter from the launch URL, so a WASM bundle can pick up configuration from the page it's loaded on (#339).
  • Native bundles bake a storage store id into the trailer (v3), binding a bundled app to its own storage namespace through the seam above (#336).
  • -w-host-eval exposes LetGoHost.eval, letting a JS host evaluate code in a live in-page image; it works in both main-thread and worker boot modes and is gated to host-eval bundles (#340).
  • WASM/mobile fixes: a newest-wins non-blocking key producer over a SharedArrayBuffer (#276, #279), viewport meta and dvh height (#291), and an overflow guard on host.html (#277).

Language and runtime

  • *command-line-args* in core (#205).
  • os/free-port returns an OS-assigned TCP port (#209).
  • str/replace and replace-first accept a function replacement, matching Clojure (#216).
  • alts! takes a :default for non-blocking select (#250).
  • Terminal SGR mouse input: enable/disable primitives and a decoder (#313).
  • set-rand-seed! reseeds the runtime RNG for reproducible rand/rand-int/rand-nth/shuffle, backed by a mutex-guarded math/rand/v2 source (#341).
  • Profiling: -cpuprofile/-memprofile flags, with LG_CPUPROFILE/LG_MEMPROFILE env fallbacks for generated builds (#331).

Compiler: native-Go IR lowering

The self-AOT pipeline now lowers most of the language straight to Go.

  • try/catch/finally (#222), deftype/defprotocol (#232), and set!/binding/var/quote (#249) lower to native Go.
  • The jank Clojure-compat surface reaches 100% lowering coverage, gated in CI (#324, #327, #317).
  • Typed direct calls: cross-namespace calls into clojure.core and lowered siblings now emit direct Go calls instead of dynamic InvokeValue, built on reified IR op-types and interface-category type classification (#310, #318, #320, #321, #322). The arc extends to hot clojure.core builtins via pkg/rt/builtins (#343), cross-package calls between lowered packages (#345), and a build-call that builds args once in callee-before-arguments order (#344).
  • Type-dispatched defmulti/defmethod lower to native Go (#325).
  • Correctness fixes: loop bytecode lowering (#214), captured-name shadowing (#247), IIFE head expansion (#254), an O(n²) closure-info blowup that timed out AOT (#280), box-as-value in :try lowering (#316), a fresh-regen nil in ir.passes.purity (#309), and empty-list () lowering to the empty-list constant rather than invoke-nil (#342).

Performance

  • Lock-free dynamic-var deref via a persistent frame-list binding stack (#220).
  • O(1)-amortized PersistentVector.pop, previously O(n) (#302).
  • PersistentMap.Equals compares in place instead of materializing a node seq (#300).
  • typeinfer runs on positional transient state rather than HAMT side tables (#301, #305, #306).
  • subs no longer materializes the whole string to runes (a124d2d).

Upgrade notes

No breaking changes. A few behavior changes are worth knowing if you embed the runtime:

  • print/pr/prn/println and the term/* ANSI control ops now route through *out*, with error sites through *err* (#206, #223). Output and terminal control that previously went straight to stdout now follow whatever those vars are bound to.
  • New WithStdout/WithStderr options on NewLetGo bind those streams at construction, scoped per Run (#207).
  • The WASM fs.writeSync output shim is retired in favor of the HostWriter seam (#231).
  • The source-paths transition warning and LG_SUPPRESS_SOURCE_PATHS_WARNING are removed (#230).

Full commit list below.

Changelog

  • 8bbb572fd0eb12436a781ed37a58caf468011772 Brew cask update for let-go version v1.10.0 (#202)
  • 5acc45e0f8fb7e4f18063eaccc392584060df49b feat(args): add core/command-line-args var (#205)
  • 9aa535498197dbab28fe1bfa42697a448d85270b fix(vm): per-namespace RWMutex — make Namespace map access concurrency-safe (F-1) (#203)
  • d6d0128f25f3c26407f1535e378b16acf4788bce feat(rt): route print/pr/prn/println through out and error sites through err (#206)
  • 8b6b545fc8512f28df8e9b26c6a802e77d442531 feat(rt): add os/free-port — OS-assigned free TCP port (#209)
  • acd4676e3f049ed1af774bbfe7aba4b999cbf6b2 fix(lower-go): make gogen_ir lowering deterministic (4 axes) (#192)
  • 531db1410a69ba20b10506c0053c17e43049acdd perf(lower-go): exact-key registry resolution (resolve-then-get, no scan) (#193)
  • a3572e311b9dcb471d6d32e3d9910d9abcd7108f feat(api): WithStdout/WithStderr constructor options (per-Run binding scope) (#207)
  • 79196be5f931d08cee27adf394291586ae71fdc2 build: cap Go heap (GOMEMLIMIT) for bootstrap/lowering targets, not just make test (#215)
  • 1809ed0d3fab1dd6d5c1e5739a95f16c53ae9358 feat(rt): str/replace + replace-first accept a function replacement (Clojure-compatible) (#216)
  • c297975d48578cd5136343f4d6fbd17cb3817393 build(lgbgen): single-pass --target=both — emit bundle + go tree from one compile (#212)
  • 439b6036e6feb5720b923e54c26ea5a303fb208c feat(vm,ir): ExecContext threading — explicit dynamic-binding + scope context, delete goid (#210)
  • 02c96609f3945cf151300b277cd867b45b71de14 Publish Homebrew formula to shared tap (#218)
  • 7beb0a403e2063c30e73dc3d4ee5c24669e2f06f fix(ir): correct ir-compile bytecode lowering of loops (4 defects) (#214)
  • 7d37167912c35ed2cca44f2a5e0f7261410482e3 feat(ir): structured Go emission — CFG → if/for tree-walk (lg-dn6u) (#213)
  • 45a9bdfdb0d36ebe70953daa382e2ec853e40e1c Makefile fixes (#219)
  • 605ccbd787759c3f8ba490fe475f26eba29a6554 feat(build): content-based regen tracking for generated .lg artifacts (#217)
  • db3c39666c06cb609ad2a02e315fe79fcb8ffe9c chore(pre-commit): add checkmake hook (#221)
  • fa20c7b8764b789234dc86286dfccb344c221adc perf(vm): lock-free dynamic-var deref via persistent frame-list binding stack (#220)
  • e314ded70ecde330a7d30b8bf67cf9a5fac28746 perf(vm): cut redundant value hashing on the lowering hot path (#224)
  • 5dfa4c4ff91631b1596c040177918599859dc4b9 feat(lgbgen): CPU profiling for the self-AOT generation process (#227)
  • 72a96a392bfa4306e68e5f2fd131a890717d12c6 fix(vm): Indexed collection protocol — correct nth dispatch + chunked-seq/conj semantics (#228)
  • ad8ff5048bce23232066049e4a0271bd4619188e feat(resolver): LG_FORCE_SOURCE_NS for incremental .lg dev loop (#225)
  • c8c7553e2f27b556f91d1010df1dd52731bd04c0 feat(rt): route term/* ANSI output through out (#223)
  • eac88b9ea59084df94136bb905047a15ec656fb5 feat(ir): lower try/catch/finally to native Go (gogen_ir self-AOT) (#222)
  • c23bae5ac8a1a0a19451695ebdaf8e79c1b0cb67 chore(source-paths): remove transition warning and LG_SUPPRESS_SOURCE_PATHS_WARNING (#230)
  • d4eb36421a00939dac839ec835e5a3812e3ccec3 fix(nrepl): report OS-assigned port under -p 0 (#229)
  • 41140ea088c3f5d08a77a621f8247a17fff8e1df feat(wasm): route out/err through a Go HostWriter, retire the fs.writeSync output shim (#231)
  • 43ff5283fb9f15ac984189c4264e0ac461cd950c docs(design): runtime I/O host-decoupling proposal (status: planning) (#233)
  • 9f8df3a764136eee5a975b8b5fc4f8d615475305 ci(perf-timeline): add workflow_dispatch for historical backfill (#236)
  • 0d9bad4f608d51faf5253b4cce021d67b9ad31b3 ci(perf-timeline): resolve dispatch ref in a script step (support short SHAs) (#238)
  • 49ba84057ba32e2986670c9a06962c342636903e feat(ir): native Go lowering for deftype and defprotocol (#232)
  • 6474580d461a1f29c643f9632adeb347adc183c9 fix(term): tolerate ENOTTY from flush Sync on a file-backed TTY (#237)
  • 80683f0b46d1603283056b89844905840544e76f build: keep the readline REPL out of GOOS=js builds (fixes #242) (#243)
  • 4e3bbbd23d21bc52ec6832dd80cf4298341de8c3 refactor(test): move e2e tests under test/e2e + pkg/bundle extraction + root-test guard (#234)
  • ad24d9b159d5565c06b9f0775c4391cf3199182b perf(ir): thread typeinfer state + preserve [:dtype T]; complete numeric typing (#235)
  • 9521ca4211d67eda304cf2e78440adc830c6919b feat(rt,wasm,api): typed HostEmitter seam — promote js/emit off the raw _lgEmit lookup (#241)
  • 164e8cb2472da62c7ec863649e2916f046ae4440 feat(rt,api): typed KeySource seam — route term/read-key + key-pending? through a host capability (#244)
  • 95aa9c5bd1c4e45f1ddab60b8c92a6bef3c55562 feat(wasm): client-owned shell — split lg-host.js, add -w-shell none (#245)
  • 7bf8f42a811bd41a6afc7b51f554e2bdaba1a9c3 feat(wasm): -w-wasm external — ship the payload as a separate streaming main.wasm (#246)
  • 0a8709780eaf11cc07e7c6306b764aecfd9fc8f8 ci(gold-differential): pin the Clojure toolchain, drop the makeplus curl|make install (#248)
  • e2aeace165085f902b7d579c7b6e97a3ca853b54 fix(ir): prefix closure-local arg names to prevent captured-name shadowing miscompile (#247)
  • cbe5abfcfa96b972678ed7d1280c8b16175da051 feat(async): alts! :default for non-blocking select (#250)
  • e712fa6fcee9ef0a456736712b835b02952cca34 docs(design): mark runtime-I/O decoupling shipped; refresh status (#261)
  • 3d40e7e01cee5da0fc1b1e4dd3680e59ee7b5ee8 fix(generate): clean lowered-tree target dir before regen (hermetic generation) (#240)
  • 9b844d3389744bdadcf3f3494d0787dcb4cb0f10 fix(genmanifest): exclude generated Go from digest; gate before regen (#252)
  • b9c3399e7b6ab968fb81c7c1636cab9b6e1b084c fix(ir): expand-all must expand fn-expression heads (IIFE) (#254)
  • 6a0a8b476ccd3b46657453ceb28693816a1c395a fix(reader): read-string skips leading no-value forms (#253)
  • d4c36cf2d526b82a36144517d4533347107ba29c fix(lang): def/declare forward-declaration promise semantics (#251)
  • 23bfd87f134c6ecd3909014d01115ebc94742f8a feat(ir): native-Go lowering for set!/binding/var/quot + self-AOT diff harness; fix cluster-A engine divergence (#249)
  • 83f650d9bd441671f806add2922b7836b2b7c094 fix(wasm): non-blocking SAB key producer — newest-wins, never block main thread (#276)
  • 329743aec1eedef6d2af8d3dbf10814a52f287ab fix(wasm): add min-height:0 to host.html #terminal so client content can't overflow the frame (#277)
  • 61264230c18e734624c563bd271f4cf2efc5c117 perf-page: relative-% timeline charts (#278)
  • d159b84bb79191175e176e6295dd036f3e3db10d fix(wasm): drop SAB key when slot still pending, don't overwrite mid-consume (#279)
  • 2a72e40d1bb84883f180828dd5f312fda5665415 docs: focus the README, move feature how-to into docs/guide/ (slice 1) (#273)
  • f7c91ee7b037615aad3a1a0b22c8e7efbe5a3e96 test(ir-stress): pre-declare deftype/defrecord/defprotocol generated names (#287)
  • c6f65ef533c51f365211fd14ae50e3d12677b5c4 fix(ir): memoize closure-info* per-nid to kill O(n^2) AOT lowering timeout (#266) (#280)
  • c9e60922c6dc74f5511eeb06c2c8fba19e3644ec test(ir-stress): eval top-level (require ...) forms so deps load (#288)
  • 8087f9979f511a9dc89266c9d84a41af00d98287 fix(wasm): host.html viewport meta + dvh height for mobile (#291)
  • 2aa2e0cc6042b69b71df2b90872491b6807dedb7 build(lgbgen): nest lowered-Go output to mirror namespace nesting (#297)
  • e9bcdcd58a9fd47b5ce603543e6c78418e81d16a fix(ir-stress): close the (do ...) wrapper on its own line in read-all (#295)
  • 5547d783eeb9cccf2a827cd865e43de62173e7d6 feat(gogen): elide callErr check for total keyword-callee calls (#294)
  • 65882d6aa9e369ce84f33c8155fd1d8869270662 feat(gogen): make the gogen macro layer usable by the IR lowering pipeline (#286)
  • 1375e8115208626625718427b9626daa807b3e3f docs: reorganize docs/ — design consolidation, layout rule, pods split
  • d67c7391ea3985e4193e6c7818fcbe9e29fe6c1c fix(term): tokenize read-key so a key burst yields one event per key
  • 46b10274521c536462eab9c053cd6cb3a5442d4a refactor(term): fold the key tokenizer into a single-pass scanner
  • ba8488ca574a6dadbada2444f177746377100119 perf(vm): O(1)-amortized PersistentVector.pop (was O(n) Unbox+rebuild) (#302)
  • 64feeec1f4e6edaab53ef64090e107a4f631c333 fix(term): flush on piped stdout
  • a124d2d0292743fefbf18a4c423b6ab703adae54 perf(rt): subs without materializing the whole string to runes
  • 5f06d3487398d84f7173333be35ddce512436b38 perf-tooling(fanout): gate on lg->go expansion ratio + portable os/ls walk (#285)
  • b67d6848826d52b3d6a470052901fbf30de8940c feat(lg): add -cpuprofile and -memprofile flags
  • 73ff475e100c1181f5e980ada1b99bc13042c604 feat(term): SGR mouse input — enable/disable primitives + decode (#313)
  • 9665f5a22c3e3c341e26b7560cc58741879b2771 perf(ir): source-infos in a positional side table, off the inst tuple (#306)
  • e55bcd948acf859139fcac729dd64930e59270e2 perf(ir/typeinfer): positional transient-vector state (Indexed-RPN), not HAMT side tables (#305)
  • d646fdfa7b66e0e2757f15d1f266fb146224972d perf(ir/typeinfer): build-deps via transients, not atom+swap!+assoc (#301)
  • 1a0432843cb514315d63fd32ad3967b3d02e1a0a perf(vm): PersistentMap.Equals walks in place instead of materializing nodeSeq (#300)
  • c3d0109ee93390562f23a58e4dfbd53397665152 ci(pages): add workflow_dispatch trigger (#319)
  • ff28da0aa255ad45296fe01697a1156656f6f635 ci(codeql): bump CodeQL action v1→v3, checkout v2→v4, add permissions (#323)
  • 892a25ca5546358fd8d3cb9a119e8eb3c70f19f6 feat(ir,scripts): jank lowering-coverage gate + canonical fn enumeration (#317)
  • 5fc3d96b4a5ea3ed79591333ff68303cbd69d07e feat(rt,wasm,api): add host-bound storage seam (#315)
  • 5dd810efad4fb715c14095fc43c51f68e1cc80dc feat(ir): lower the full jank Clojure-compat surface — coverage gate 65% → 100% (#327) (#324)
  • c553565076fa97dab90120125769bacb4122b838 ci(perf): same-runner A/B microbenchmark check on PRs (#328)
  • 511a0bfbd986909cf90dcb72ba41949e88d85faa perf-page: CPU-tier filter and selectable baseline; release-baseline recapture (#329)
  • 4714b53c58e94a3f4d1f75df5fd3816c32ad7520 perf(bench-ratchet): add named -profile with a curated pr-fast profile (#330)
  • 814947e7bb137bb77a0af97f39ea87e9a5e5abe2 feat(lg): LG_CPUPROFILE/LG_MEMPROFILE env fallback for profiling builds (#331)
  • 15599cead8893deaccbb0523717e05a517604a0a perf(lgbgen): default GOGC=800 for the alloc-heavy lowering run (#337)
  • 0771a46345438d15d9c9e6e589515955c2f4bc7d perf(bench-ratchet): retune pr-fast to count 3 / benchtime 500ms (#333)
  • 1b38cf92e7e53382e9b9a720ac5e8b656aa81efc ci(perf): use the pr-fast profile for the PR benchmark gate (#334)
  • 40132647f649a88c1b608e6452001fbdfca3b218 feat(ir): interface category types + collection-literal classification — typed-call foundation (#270) (#320)
  • aad700e0416c32a790ff0fbb08076fc6b1616fdf feat(ir): typed lowered-sibling direct calls (emission half) (#321)
  • dcb1b2d4021f3b07619129f9bbe48f0d05c92f1c feat(ir): cross-ns typed direct calls — emit machinery (#272) (#322)
  • 4d79b62dfa959f9a19a1d959c8ab77aceb0c5f33 feat(ir): native-direct cross-ns calls into clojure.core builtins (#271) (#318)
  • 54ecbcec6d7559a0446331098a9d100e30ec212e feat(ir): reify IR ops as native deftype op-types — S3 walking skeleton (#268) (#310)
  • 0864e19f1c54edcd0c50c6d90753a9e389f50206 fix(lgbgen): bundle ir.passes.purity so a fresh regen doesn't nil dce/removable? (#258) (#309)
  • b2b253474fa3acc3b7f88454e09ffd8c7b1c3e29 fix(ir/lower-go): guard box-as-value in :try op lowering (#316)
  • 6bc46ddc474628be473e0dfa4fde98fa6a77e567 test(determinism): add lowering determinism property test (#175) (#299)
  • ba07b3375f599b491cf0dbfe36179248e53652f2 feat(docs): docs-status report for the judgement layer (#338)
  • 0c276fb9da0c9edc25aba485f2e86121a0f1b74b feat: add js/url-param for reading the launch URL from a WASM bundle (#339)
  • 093806b0020eb551e5cdb6116fcb4367702ca197 feat(bundle,lg): bake storage store id into native bundles (v3 trailer) (#336)
  • 30feb90949a6c4eaccf4991ed424aa4a17910fef feat(rt): add set-rand-seed! with a mutex-guarded math/rand/v2 source (#341)
  • 5ba842f59f5e75fe6681eca7a005ecdd3da5fdf5 fix(ir): empty-list () lowers to the empty-list constant, not invoke-nil (#342)
  • 9b3fc518b090b075fae0379dd39aed870326b841 docs: add docs-status agent skill + Claude Code discovery shim (#346)
  • 30529952b131fa5f3fb25a24d847fc005d97fc80 feat(ir): native Go lowering of type-dispatched defmulti/defmethod multimethods (#326) (#325)
  • 60c27cf2c24aa0f61eae4c5ab9ae86be0d0bb696 feat(ir,rt): Go-lower clojure.core + direct-call hot builtins via pkg/rt/builtins (#258) (#343)
  • a81322dd93c644bed174edc5fe9a9d308eb0f6b4 feat(ir): build args once in build-call, callee-before-arguments (#344)
  • 36b8f47a1a0ec23d7f13b430e0288b217dad1f5c feat(ir): cross-package direct calls between AOT-lowered packages (#258) (#345)
  • 25ccf5505588cfd6fd0c1ed7e870446ae1ec4a2a feat(wasm): add -w-host-eval to call into a live image via LetGoHost.eval (#340)
  • 2e8b6683c32acd3a485bca555af5c9635c24193e ci(release): isolate slow lowering e2e tests in a dedicated step (#347)
  • 723fe22f28c26b5c087e474b96b5a9f43f2bf104 docs: route the gogen_ir microbench README in the topical map (#349)

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track I made a Clojure-like language in Go, boots in 7ms

Get notified when new releases ship.

Sign up free

About I made a Clojure-like language in Go, boots in 7ms

All releases →

Related context

Beta — feedback welcome: [email protected]