Releases: leifericf/mino
v0.422.1
bits: rename kw_match to bits_kw_match for amalgamate src/prim/module.c already defines a file-local static kw_match. Per-file translation units link cleanly, but the amalgamate task concatenates every .c into a single dist/mino.c TU where the two statics collide as a redefinition. Release-gate caught it post-push; local tests miss it because they go through the per-TU path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.388.0
changelog+version: cycle close Final tag of the pre-1.0 Embedder UX cycle. Per-phase tags v0.382.0..v0.387.1 carry the granular notes; v0.388.0 is the roll-up with pointers to the mino-examples cookbook chapters and the new mino-site Internals page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.381.1
Fixes a long-standing CI hang documented in .local/BUGS.md as
"CI hang #2: tests/run_migrated.clj on ubuntu-24.04". The hang was
deterministic on GHA ubuntu-24.04 runners (both x86 and arm) and
masked on macos-14 plus Apple Silicon Docker. Reproducer: a single
(dosync (send a fn)) followed by (await a) would block forever
on the very first iteration.
Root cause
agent_worker_ensure released agent_mu between spawning the
worker pthread and the caller's subsequent agent_enqueue call.
A freshly-spawned worker could win the race to agent_mu, see an
empty runq, set worker_alive = 0, and exit before the producer
ever published its action. The producer's later agent_enqueue
then pushed the node + bumped in_flight into a runq with no
consumer; (await a) waited on a condition that nothing would
ever signal.
Fix
Merge the worker spawn and the enqueue into a single
agent_enqueue critical section. The producer:
- Takes
agent_mu. - Decides whether a worker is needed (
worker_alive == 0), and
if so reserves a thread-budget slot atomically. - Pushes the action node and bumps
in_flight(still under
agent_mu). - Releases
agent_mu. - Calls
pthread_createonly after the node is published.
The new worker's first agent_mu_lock therefore always observes
a non-empty runq for the producer that spawned it. Thread-budget
refusals revert the enqueue and free the node before throwing so
(await a) is never stranded on an action that no worker will
process.
The dead agent_worker_ensure and its inline spawn body are
removed. agent_worker_reap_pending stays — mino_agent_quiesce_workers
still uses it on state teardown, and the new agent_enqueue calls
it under agent_mu (with a state-lock yield around the
pthread_join) when the previous worker has exited.
Regression test
Added tests/migrated/dosync_send_repro.clj to mino-tests —
five iterations of the dosync-send-await pattern with explicit
progress logging. Pre-fix, this hangs after the first
[repro] iter 0 before await on Linux CI. Post-fix, all five
iterations complete in ~50ms locally and the full Migrated suite
passes on every CI runner.
Verification
- Full mino test suite green (1371 tests, 4828 assertions).
- Local linux/amd64 + linux/arm64 Docker run the migrated suite
in ~24s end-to-end; isolated repro completes in 50ms.
v0.381.0
Closes the file/function-level cleanup the architecture cycles
deferred. Behavior-preserving throughout — every commit kept the
full test suite green (1371 tests, 4828 assertions) and the mino-tests
batteries (adv-test 18/18, diff-test 7/7, fault-inject 5/5,
test-migrated 483 tests / 3397 assertions).
Hygiene
Stripped tracking metadata out of source comments so they describe
intent instead of release history:
vX.Y.Zversion stamps removed from 15+ sites acrossruntime/,
prim/,eval/, andeval/bc/jit/.Phase N/Cycle Nbreadcrumbs removed fromeval/fn.c,
eval/defs.c,runtime/internal.h.- "legacy" annotations rewritten without the legacy framing
(transient wrappers, JIT slab-vs-mmap path, regex string source,
reader depth cap,gc_tick_should_suppress). gc_sweepdoc aligned with what the body actually does
(it does walk all_young, both to clear major-frontier mark bits
and to tally live young bytes).
Identity-by-name for primitives
Pointer-comparing as.prim.fn to detect canonical prims was
fragile: it would treat two distinct argv-only prims (both with
fn == NULL) as equal. Switched every identity-check site to the
stable registered name:
mino_eqforMINO_PRIMis now allocation-pointer equality
(each prim is allocated once per state, so this is correct and
fastest).compile.c's pure-prim shadow-check usesstrcmpagainst the
recorded name.sequences.c'sclassify_subseq_test,reduce_int_kind_from_fn,
andpipeline_fast_callableall match by name.reduce_step's per-element identity check was hoisted into a
reduce_int_kind_t kindparameter the caller classifies once,
so the inner loop runs a small switch rather than per-element
strcmp.- The install-time dual-fill hack in
prim/install.cno longer
carries the "identity-pin" justification (it stays as a pure
ABI-compat shim until the cons-spine port lands).
Dispatch tables / per-handler extraction
Five tag-dispatch switches reshaped so each branch reads as a
named unit:
| Switch | File | Shape |
|---|---|---|
mino_iter_next |
collections/iter.c |
{kind, iter_step_fn} table |
read_dispatch |
eval/read.c |
{char, read_dispatch_fn} table |
mino_print_to |
eval/print.c |
per-tag print_* helpers, switch driver |
mino_eq |
values/val.c |
eq_cross_type helper + identity group |
prim_conj |
prim/collections.c |
per-kind conj_* helpers |
God-function splits
compile_call_impl was the Tier-1 target. 614-line dispatcher
shrinks to a 77-line orchestrator with nine try_emit_* helpers
(arith unop, n-arity arith, two-arg binop, collection-op
arity-2/1/3, keyword-as-fn, IC-cached call, plain call) plus two
{name, opcode} tables that fold seven shape-identical 2-arg ops
(nth, get, conj, dissoc, conj!, dissoc!, disj!) and three 1-arg
read ops (first, count, empty?) into single dispatch entries.
| Function | Before | After |
|---|---|---|
compile_call_impl |
614 | 77 |
main |
488 | 296 |
mino_print_to |
486 | ~80 |
bind_vec_destructure |
193 | 80 |
apply_callable (PRIM) |
54 | 3 |
run_repl extracted from main. apply_prim_cons extracted
from apply_callable (with a shared current_call_location
helper for the file/line/col lookup that fn.c repeats around every
push_frame). vec_destructure_args extracted from
bind_vec_destructure — the value-to-cons normaliser is now its
own ~80-line helper with three named cases (vector / map-entry /
lazy-or-chunked).
The FN/MACRO branches of apply_callable, bc_run_dispatch_from
(~1180 lines), and mino_jit_compile_inner are documented as
deferred — each carries tail-call trampoline / per-pass-state
invariants that are real changes rather than pure motion.
Process notes
The Cycle 2 plan assumed all primitives already used the argv ABI
and that fn was kept only for identity tagging. Survey at
execution time showed the opposite: 344 of 386 prim entries use
the cons-spine fn only. Dropping the fn field is its own
multi-file port and was carved out into a follow-up cycle; this
release ships the identity-by-name fix that makes that future
port mechanical instead of behavioral.
The bc_run_dispatch_from / mino_jit_compile_inner / quasiquote
splits are documented in .local/micro-refactor-plan.md as
deferred targets — each is sizable structural work that earns its
own cycle.
Verification. Full mino test suite green (1371 tests, 4828
assertions). mino-tests adv-test (18/18 probes), diff-test (7/7),
test-fault-inject (5/5), test-migrated (483 tests / 3397
assertions) all green after companion fix in mino-tests
(gc_generational_test switched to a mapv inc (range N)
workload that still exercises the generational invariant under
mino's now-fused lazy-seq paths).
v0.323.0
changelog: post-jit-2 cycle close Seven-release sub-cycle (v0.317-v0.323) wrapping. Side-exit deopt stencil landed cleanly: real-workload corpora now show 100% reason=OK eligibility; the full test corpus contributes one OK_WITH_DEOPT and zero hard rejections. JIT loop cancellability closes the v0.312 audit gap. Control-flow stencil work measurement-gated and shipped zero new stencils honestly per the 7% movement threshold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.255.29
diag+bc+gc: restore bc_current_bc across throws + fair safepoint yield
Three follow-ups to v0.255.27's safepoint poll + diag-location
work, surfaced by v0.255.28 CI on a 2-CPU ubuntu-24.04 x86_64.
1. ctx->bc_current_bc could outlive the BC fn it pointed at when a
throw longjmped past an inner BC frame's normal exit-time
restore. normalize_exception (now called on every catch with
the v0.255.27 location attachment) then dereferenced a soon-
to-be-freed bc_fn_t, surfacing as heap-use-after-free in
mino_bc_source_lookup under ASan. Two fixes:
- BC VM's OP_PUSHCATCH setjmp landing now restores
ctx->bc_current_bc to the current fn's `bc` before
normalize_exception runs.
- gc_mark_runtime_globals marks ctx->bc_current_bc as a GC
interior pointer for every live ctx (main + workers). Covers
cases the setjmp restore can't (eval_try tree-walker
landings, host pcall re-entry).
2. The BC safepoint poll's auto-yield path
(mino_yield_lock + sched_yield + mino_resume_lock) was too
tight on 2-CPU runners: POSIX mutex_unlock/lock isn't fair, so
the yielding thread re-grabbed state_lock ahead of waiters.
The busy-spin-doesn't-starve-siblings test hung on
ubuntu-24.04 x86_64. Replaced sched_yield with a 100us
nanosleep (POSIX) / Sleep(0) (Win32) between unlock+lock so
the OS scheduler has a chance to hand off.
3. tests/async_smoke_test.clj's busy-spin n calculation
floored at 2 even on 2-thread hosts; now uses
(max 1 (min 3 (- limit 1))) so it always fits the budget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.163.0
bc: consolidate IC consumers behind shared resolve helpers Two static helpers in src/eval/bc/vm.c centralise the IC cache discipline: ic_resolve_global(state, bc, slot, env, dyn_active) carries the dyn -> env -> cached -> resolve cascade for OP_GETGLOBAL_CACHED and OP_CALL_CACHED, plus the write-barrier refill and the S->ic_gen snapshot check. ic_resolve_protocol(state, bc, slot, first_arg) carries the protocol-method cache for OP_PROTOCOL_CALL_CACHED and OP_PROTOCOL_TAILCALL_CACHED: atom deref, type-discriminator computation, pointer-pair cache check, map_get_val miss path with :default fallback, three-field write-barrier refill, and the MPR001 / MPR002 diagnostics. GC side: the IC-slot walk that used to duplicate across the MINO_FN and GC_T_BC_FN walker arms in src/gc/driver.c now funnels through a single static gc_mark_bc_ic_slots helper, so the slot-kind to field mapping lives in one function. No behavior change. Matrix neutral across the eval + protocol bench suite (within macOS run-to-run noise of ~10%). The substantiation pays off whenever a fourth IC consumer lands (e.g. a cached tail-call variant or a profile-driven specialisation) -- the new consumer plugs into the existing resolve / refill / GC-scan machinery instead of replicating any of it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.151.1
release: cut v0.151.1 Bumps the patch version to 0.151.1 and renames the Unreleased CHANGELOG section to v0.151.1 — Embedding API Hardening. Five adversarial-test follow-ups on the v0.151.0 embedding-API revamp.
v0.151.0
v0.151.0
v0.150.0
release: cut v0.150.0 Stabilization cycle landing the verified-real findings from a ship-readiness review of the C codebase. Highlights: nine more realloc-overwrite leaks in src/prim/string.c follow the canonical temp-pointer pattern; a checked-size helper trio (checked_add_sz / checked_mul_sz / checked_double_sz) guards growth arithmetic across env, state, module, eval/read, and prim/io; gc_pin asserts on overflow in debug builds; mino_safepoint_poll becomes a static inline; add-load-path! prefers memcpy when the length is known; the stale dyn_snapshot TODO is replaced with a description of what the field actually holds; embed_stm_test no longer crashes on small- integer agent values (six sites route through mino_to_int). See CHANGELOG.md for the full set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>