This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
Affected surfaces
Summary
AI summaryBroad 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 nonelets the host page supply its own shell;lg-host.jsis split out so a client can drive the runtime directly (#245).-w-wasm externalships the payload as a separate streamingmain.wasmrather than inlining it (#246).- Typed host seams: output through a Go
HostWriterfor*out*/*err*(#231), aHostEmitterforjs/emit(#241), aKeySourceforterm/read-keyandkey-pending?(#244), and a host-bound storage seam (#315). js/url-paramreads 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-evalexposesLetGoHost.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
dvhheight (#291), and an overflow guard onhost.html(#277).
Language and runtime
*command-line-args*incore(#205).os/free-portreturns an OS-assigned TCP port (#209).str/replaceandreplace-firstaccept a function replacement, matching Clojure (#216).alts!takes a:defaultfor non-blocking select (#250).- Terminal SGR mouse input: enable/disable primitives and a decoder (#313).
set-rand-seed!reseeds the runtime RNG for reproduciblerand/rand-int/rand-nth/shuffle, backed by a mutex-guardedmath/rand/v2source (#341).- Profiling:
-cpuprofile/-memprofileflags, withLG_CPUPROFILE/LG_MEMPROFILEenv 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.coreand lowered siblings now emit direct Go calls instead of dynamicInvokeValue, built on reified IR op-types and interface-category type classification (#310, #318, #320, #321, #322). The arc extends to hotclojure.corebuiltins viapkg/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/defmethodlower 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
:trylowering (#316), a fresh-regen nil inir.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.Equalscompares in place instead of materializing a node seq (#300).- typeinfer runs on positional transient state rather than HAMT side tables (#301, #305, #306).
subsno 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/printlnand theterm/*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/WithStderroptions onNewLetGobind those streams at construction, scoped perRun(#207). - The WASM
fs.writeSyncoutput shim is retired in favor of theHostWriterseam (#231). - The source-paths transition warning and
LG_SUPPRESS_SOURCE_PATHS_WARNINGare 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 freeAbout I made a Clojure-like language in Go, boots in 7ms
All releases →Related context
Related tools
Beta — feedback welcome: [email protected]