Skip to content

Releases: leifericf/mino

v0.422.1

21 May 10:43

Choose a tag to compare

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

20 May 11:10

Choose a tag to compare

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

20 May 06:45

Choose a tag to compare

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:

  1. Takes agent_mu.
  2. Decides whether a worker is needed (worker_alive == 0), and
    if so reserves a thread-budget slot atomically.
  3. Pushes the action node and bumps in_flight (still under
    agent_mu).
  4. Releases agent_mu.
  5. Calls pthread_create only 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

20 May 05:02

Choose a tag to compare

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.Z version stamps removed from 15+ sites across runtime/,
    prim/, eval/, and eval/bc/jit/.
  • Phase N / Cycle N breadcrumbs removed from eval/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_sweep doc 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_eq for MINO_PRIM is now allocation-pointer equality
    (each prim is allocated once per state, so this is correct and
    fastest).
  • compile.c's pure-prim shadow-check uses strcmp against the
    recorded name.
  • sequences.c's classify_subseq_test, reduce_int_kind_from_fn,
    and pipeline_fast_callable all match by name.
  • reduce_step's per-element identity check was hoisted into a
    reduce_int_kind_t kind parameter 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.c no 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

18 May 05:48

Choose a tag to compare

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

17 May 08:13

Choose a tag to compare

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

14 May 15:56

Choose a tag to compare

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

13 May 14:11

Choose a tag to compare

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

13 May 12:16

Choose a tag to compare

v0.151.0

v0.150.0

13 May 09:06

Choose a tag to compare

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>