Skip to content

v1.15.1: wasm/exports, dot interop over wasm exports, zwasm fork main, README rewrite - #18

Merged
BuddhiLW merged 28 commits into
clojurewasm:mainfrom
BuddhiLW:main
Oct 21, 2026
Merged

BuddhiLW merged 28 commits into
clojurewasm:mainfrom
BuddhiLW:main

Conversation

@BuddhiLW

Copy link
Copy Markdown
Contributor

Brings upstream main to v1.15.1 (released from BuddhiLW/ClojureWasm).

  • wasm/exports lists a module's exports as {:name :kind :params :results} (ADR-0203).
  • (.f h args) calls a wasm export by name on both backends.
  • Round 7 fixes: type redefinition and identity, VM error locations.
  • zwasm pinned to BuddhiLW/zwasm main d74e63b4.
  • README rewritten.

Full gate passed on x86_64-linux and aarch64-macos (BuddhiLW#67).

BuddhiLW and others added 28 commits October 13, 2026 19:04
release: v1.15.0 (rounds 5-6)
…s (ADR-0194)

tools.reader's inspect* dispatches a multimethod on LazySeq, Cons and the
three JVM inner seq classes; a defmethod on a class symbol resolves it at
analyze time. Pins that LazySeq and Cons are native exact-tag classes whose
methods are reached, and that PersistentVector$ChunkedSeq,
PersistentArrayMap$Seq and PersistentHashMap$NodeSeq resolve as opaque
class values that no cljw value is an instance of (AD-066 / AD-067).

Supersedes the 2026-09-09 WIP b3b332f, whose Cons-as-opaque assertions
predate f8837bc. Qualified-name assertions checked against JVM Clojure.
… error locations in the diff oracle (ADR-0036)

The ADR-0036 dual-backend oracle compared only the Zig error tag for a
failing case. It never checked WHERE the error was raised, so the VM could
raise at 0:0 while TreeWalk reported the right line:column, and nothing
caught it.

evaluator.compare keeps the error-identity `equal` (6e1c72f) and adds
tree_walk_loc / vm_loc / loc_equal. Each position is sampled with
error_info.peekLastError() right after its own backend run. runOnce does
not reset the threadlocal slot, so compare clears it before each run;
otherwise a stale payload would be read as this run's position. A run
that fails without publishing a payload reports 0:0.

Every diff fixture check (check, checkEqual, checkRaises) now asserts
loc_equal. The new "error source location survives BOTH backends" test
requires a real (non-zero) and equal position for set!, op_method_call,
a constructor arity failure, and multi-line forms. The staging binary
(b59d336) raises the set!, .member and ctor cases with no location on
the VM, so this test fails without the VM changes below.

VM: every error_catalog.raise that passed an empty SourceLocation and has
a located TreeWalk twin now uses instr_loc (as D-326 did for callFn):
op_set_var, op_set_field, op_method_call, op_static_method_call (and its
callFn), op_ctor_call (constructInstance), op_ns_import, the require/refer
path, op_invoke_builtin, and the slot_out_of_range guards. The compiler
stamps the form's loc before op_set_var, op_set_field, op_method_call,
op_static_method_call and op_ctor_call, so the instruction reports the
form, not its last argument. The stack_overflow raises keep `.{}`
because their TreeWalk twins are unlocated too.

Port of 04b0d0b (worktree-agent-a96eb3d50b10efd45), never landed.

Smell-audited: 2: Changes the locations passed to existing raise sites and adds three CompareResult fields plus one assertion helper; no new branching. vm.zig, compiler.zig and diff_test.zig are past the size ceiling; this grows them by a few lines with no new concept.
Smell-audited: 0: comment punctuation only, no code change.
…y its simple name (ADR-0201)

A user class IS its TypeDescriptor. instance?, .isInstance, class isa?
and multimethod dispatch now answer by descriptor identity
(class_name.isInstanceOfClass / classIsaOf / descriptorIsa), so they agree
with (= (class x) C) for a redefined record, a namesake defined in another
namespace and a reify class. The record-interface, IFn and clojure.lang.Fn
arms route through descriptorIsa instead of bypassing identity.

multimethod: an instance dispatch value relates to a Named value only by
derive; the D-082 instance-vs-Named simple-name arm is gone.

The type_redefinition diff corpus pins every redefinition scenario on both
backends. Ported from swarm/td-uaf (94a2ed45, d4d028a3, c0932e99).

Smell-audited: 2: replaces name-keyed membership checks with one descriptor-identity predicate and deletes the D-082 name arm; no new branching surface.
… was written in ([CLJW-CTOR-ANALYZE-TIME])

analyzeCtorCall records definer_ns, the namespace a bare, non-imported
(Name. args) form is analyzed in. Both backends carry it to
constructInstance (tree_walk evalConstructorCall, vm op_ctor_call through
CtorEntry.definer_ns), which looks up <definer_ns>.<Name> before any other
reading. So aaa/->Point and a (Point. x y) inside aaa build aaa.Point even
after bbb registered a namesake of a different shape (ADR-0198, ADR-0201).

The ctor site's definer_ns is serialized: bytecode VERSION 12 -> 13
(docs/spec/formats/chunk-v13.edn). A unit test pins the definer_ns-first
lookup.

Tests: user_class_identity_test.clj (new) covers instance?, isa?, class
equality and dispatch across redefinition and namesakes. The six scenarios
of the former type_descriptor_redefinition e2e (never landed) live in
type_redefine_test.clj, so the e2e count does not grow.

Smell-audited: 2: threads one namespace field from the analyzer to the existing constructor lookup and bumps the chunk format once; no new dispatch path.
Redefining a deftype/defrecord no longer frees a descriptor live
instances still use, namesakes in different namespaces are different
classes, and a bare constructor binds its defining namespace. Pinned by
type_redefine_test.clj, user_class_identity_test.clj and the
type_redefinition corpus. rt.types_by_simple stays for the bare-name paths
that are not ctor sites (card 20260909231443-1ba4303c).
# Conflicts:
#	src/eval/backend/vm.zig
…rror locations) under [Unreleased]

Smell-audited: 0: release notes only, no source change
(wasm/exports h) returns a vector of maps in export-section order. A
function answers {:name :kind :func :params [...] :results [...]}, any
other export {:name :kind :memory|:global|:table}. Signatures come from
Loaded.exportSig, the probe wasm/call marshals through, so what is listed
is callable at exactly the listed arity.

The export list is decoded at load, while the module bytes are alive:
zwasm's section view borrows them, and wasm/load frees them right after.

Tests: exports.zig unit tests (interp + jit, bytes freed before use) and
test/e2e/phase16_wasm_exports.sh (add.wasm, the C kernel, a bad handle).

Smell-audited: 1: new builtin in its own file, one registration line, no change to existing call paths
(.add h 40 2), (. h add 40 2) and (. h (add 40 2)) on a wasm_module value
call the export named by the method symbol, verbatim, so C names such as
sum_f64 work as written.

Runtime.wasm_member_call is a hook both backends consult on a method-table
miss (VM op_method_call, TreeWalk evalInstanceMember). The wasm surface
installs it at register, so it exists only under -Dwasm (F-001) and the
eval zone never imports a surface. wasm/call and the hook share one
callExport path: signature probe, arity check, marshalling, fuel and trap
mapping are not duplicated, and errors are identical.

Test: test/e2e/phase16_wasm_dot_interop.sh with its fixture (the three
spellings, the C kernel, missing export / arity / trap parity with
wasm/call, non-wasm receivers unchanged).

Smell-audited: 2: one runtime hook field instead of a surface import into eval; shared callExport extracted from wasmCallFn
wasm/exports and dot-interop are value assertions, not process-boundary
behaviour, so per decision 20260911144411-22d9cd94 they are deftests in
test/clj/suites/ (wasm_exports_test, wasm_dot_interop_test) instead of two
new e2e scripts. That keeps check_e2e_count at its 322 baseline.

data/placement.yaml is regenerated for the new wasm/exports var.
Six changes agreed by the newcomer, clojurist and editor reviewers in
agora d666cca9:

1. Order and navigation: Install and Quickstart follow the intro, a
   one-line table of contents is added, News shrinks to one paragraph,
   and License and credit carries the full handover account.
2. Quickstart keeps the wasm/call line and adds the ADR-0203 dot form
   (.add m 40 2), marked as arriving in the next release.
3. Wasm FFI section: wasm/exports as the discovery step and
   (.sum_f64 m buf 4), both marked next release, with the dot-interop
   semantics (ADR-0203); every elided output replaced by the real one.
4. Accuracy: startup figures only under Numbers; string-path :require
   marked cljw-only; "What does not" names core.async, java.time.format,
   the permissive clojure.edn/read-string and (clojure-version) "1.12.0".
5. Where it shines trimmed: the sandbox example runs a shipped module
   (kernel.wasm with a cap, trap.wasm caught); Native tools shows a real
   sh result; padding closer dropped.
6. What runs trimmed: the Maven resolver paragraph is two sentences
   plus a CHANGELOG link; "test.check, ..." becomes "test.check and more".
The v1.15.0 tag sits on main's release merge 32125e3, which staging never
contained, so from staging the newest reachable tag was v1.14.12 and
check_changelog_reachability failed (the CHANGELOG already opens with
[1.15.0]). The merge carries no file change: 32125e3 has the same tree as
its staging parent e068c17.
329 lines to 219. The page now opens with install, then the C function
compiled to Wasm and called with dot interop, then the architecture
diagram, the size and startup charts, and the one-program-two-runtimes
diagram with the Groth16 table compressed to three rows. Use cases are
five bullets; what runs, docs and credit are each one short block.
Every figure and link carried over unchanged; nav anchors match headings.
The partial result vector and each export map lived only in Zig locals
while the builder allocated, so a collection mid-build swept them: under
CLJW_GC_TORTURE_ALLOC=1 the call panicked with "@memcpy arguments alias",
and in a long suite run the vector came back as one map plus nil entries.
rt.gc.enterFabrication defers collection until the builder returns, the
same bracket every other multi-alloc builder uses (ADR-0150).

Smell-audited: 1: missing GC bracket in a new builtin, fixed with the existing idiom
Fork main is fast-forwarded to upstream zwasm/zwasm main. Upstream now
carries all three fixes the fork's `cljw` branch existed for (851cd0e7
component re-export, f3067d03 wasip2 metadata-hash, 767b808a
PreparedWasi), so the pin leaves `cljw` for main. The version string is
still 2.7.0 and no newer tag exists; the pin returns to a tag when one is
cut.

Smell-audited: 0: dependency pin bump, no source change
The header shows the ClojureWasm logo beside the maintainer's (linked to
github.com/BuddhiLW). [Unreleased] records wasm/exports and dot interop
(ADR-0203), the zwasm pin move to fork main d74e63b4, and the shorter
README; the README now says those features need v1.15.1, the patch the
release preflight cuts from 1.15.0.
The same mark the presentations use (pgb-logo.svg, exported as
~/PP/logo/logo.png). The PNG keeps its white background so it stays
visible on GitHub's dark theme.
Round 7: type redefinition and identity (D-587), VM error locations (ADR-0036), seq class dispatch test
Release: wasm/exports, dot interop over wasm exports, zwasm fork main, README rewrite
@BuddhiLW
BuddhiLW merged commit 8621cd0 into clojurewasm:main Oct 21, 2026
2 checks passed
BuddhiLW added a commit that referenced this pull request Oct 22, 2026
The upstream mirror clojurewasm/ClojureWasm carries this workflow, so
merging #18 there cut its own v1.15.1 build and its tap job tried to
rewrite Formula/cljw.rb with that build's checksums. The formula's urls
name BuddhiLW/ClojureWasm releases, whose artifacts hash differently
(linux cb7ad4a5 here vs 586396f7 upstream), so a successful push would
have broken brew install. It failed instead with a 403: the mirror has
no TAP_DEPLOY_KEY. Guard the job on github.repository; it is skipped,
not failed, elsewhere.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant