Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 53 additions & 12 deletions docs/WAM_GO_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ WAM pipeline via `prefer_wam(true)`.
|---|---:|
| `src/unifyweaver/targets/wam_go_target.pl` | ~4.3k |
| `src/unifyweaver/targets/wam_go_lowered_emitter.pl` | ~0.8k |
| Dedicated tests | ~23 files |
| Dedicated tests | ~26 files |

## What's shipped

Expand Down Expand Up @@ -88,7 +88,7 @@ classes that are fleet-wide suspects. Go's audit:
|---|---|---|---|
| A1 | `sub_string/5` builtin missing | **verified missing** | Go has `sub_atom/5` (`state.go.mustache:2792`) but no `sub_string/5` |
| A2 | Y-register clobber across `Call` of a no-`Allocate` fact | **aliasing form: verified (structural), Call Y-save is a partial mitigation, X101≡Y1 not hit by uw-resolve. Frameless-Y form: REPRODUCED as a live wrong answer on the lowered lane → FIXED 2026-09** | encoding `X_n→n+99 / Y_n→n+199`, so **X101 ≡ index 200 ≡ Y1**. Call snapshots Y 200..299 and Proceed restores; Execute does **not** push (LCO). Choice points snapshot the Y-save stack. The numeric alias is unchanged. See the frameless-Y section below. |
| A3 | `Execute` of a builtin doesn't return to the continuation | **handled for known builtins; `call/1` now classified** | `BuiltinExecute` takes Proceed's return path **including** `popCallFrame`. Residual: a missed classifier entry still silently fails. `call/1` is `wam_go_direct_builtin` → `BuiltinCall`. `member/2` in uw-resolve is `BuiltinCall`. |
| A3 | `Execute` of a builtin doesn't return to the continuation | **handled for known builtins; P3 helpers classified; unknown-builtin warn knob landed** | `BuiltinExecute` takes Proceed's return path **including** `popCallFrame`. Residual: a missed classifier entry still fails the query; `UW_WAM_WARN_UNKNOWN=1` now prints `[wam_go] call|execute of unresolved goal NAME failed` (off by default). `call/1`, `maplist/2-4`, `predsort/3`, `functor/3`, `arg/3`, `=../2` are `wam_go_direct_builtin`. Lowered `pred_to_go_call` also falls back to `executeBuiltin` then warn. |
| A4 | String fidelity | **rung 0** | `value.go.mustache` has Integer/Float/Atom/Compound/Structure/List/Ref/Unbound — no string type; D37's double-quoted literals intern as atoms |

Pattern lane: `go_target.pl` compiles facts from `clause(Head, true)`
Expand Down Expand Up @@ -234,16 +234,51 @@ runs `wpick` on the interpreter lane only and says why.

## Whole-program exercise: uw-resolve (`examples/pkg_resolver/go/`)

P0.5 resolver compiled through `wam_go` (`prefer_wam(true)`). JSON shim
is term↔JSON IO only. Corpus **39/39** vs SWI; seeded differential
**2400/0**. Additional runtime bugs the program forced (not in the A2
table): empty-list `GetConstant` vs `*List`; `sort/2` unique-collapsing
compounds; `switch_on_structure` emission using `Val` instead of
`Functor`; 4-arg `begin_aggregate`; `allocVarId` aliasing driver Idx
10000–10999 (B3 `sort/2` unifying Acc with `[]`). **B3** on the 5k
catalog (`0xc0ffee01`): Go load **0.060s** / resolve **4.604s**, same
10-package selection as SWI (load **0.428s** / resolve **0.008s**). See
`examples/pkg_resolver/go/README.md`.
P3 resolver compiled through `wam_go` (`prefer_wam(true)`). JSON shim
is term↔JSON IO only (deb versions, Provides 3-/4-ary, alternatives
groups, `catalog/6|9|10`, blocked `providers`/`alternatives` shapes).
Corpus **51/51** vs SWI; seeded differential **2600/0** (2400 `g*` @
`0xa5b6c7d8` + 200 `p3g*` @ `0xdeb00001`). D59 `index_threshold(64)`
comes free with the current `resolver.pl`.

P3 builtin inventory (generated WAM → runtime → `wam_go_direct_builtin`):

| Name | Found | Implemented | Classified |
|---|---|---|---|
| `maplist/2,3,4` | missing; compiler emits `BuiltinCall`/`Call` | yes, meta-calls user preds via `invokeGoalOnce` | yes |
| `predsort/3` | missing; resolver `cmp_ver/3` for `deb/3` | yes | yes |
| `functor/3`, `arg/3`, `=../2` | implemented, unclassified (A3) | already in `executeBuiltin` | now classified |
| `string_codes/2` | present; empty result was `*List{}` | empty goes through `listFromItems` | already classified |
| `memberchk/2` | already OK | — | — |

`UW_WAM_WARN_UNKNOWN=1` (off by default) prints
`[wam_go] call|execute of unresolved goal NAME failed` after the
label/foreign/fact miss — additive, mirrors Rust.

Bugs this program forced beyond the P0.5 table:

| Symptom | Cause | Fix | Probe |
|---|---|---|---|
| `provides_virtual_only` / first-listed / versioned-ok → `{fail:true}` | `pick_need` indexes on Mode; `classic` is switch `"default"` with two clauses; Pc rewrite jumped to `idx+1` then `indexedClauseBodyStart` skipped `TryMeElse` | label-form default falls through (`PC++`); Pc form keeps the jump when the target is a try/retry head | `tests/test_wam_go_switch_default_chain.pl` |
| 40 `p3g*` differentials `{fail:true}` (corpus still 51/51) | `predsort` meta-called `cmp_ver`; `invokeAtPC` smashed A2; `Unify(getReg(2), Sorted)` compared a version to a list. Single-element lists and `compare/3` never ran the less-func — corpus P3 rows filtered to one candidate | capture A2 before the comparator; restore regs after each `invokeGoalOnce`; leftover CPs truncated | `tests/test_wam_go_maplist_predsort.pl` (`gpreduser`) |

**B1–B3** (this Cloud Agent VM, SWI 9.0.4, Go 1.22.2, Node v22):

| Bench | Number |
|---|---|
| B1 corpus wall | **0.053s, 51/51** |
| B2 differential | **2600 cases, 0 divergences**; SWI **1.671s** / Go **17.179s** |
| B3 5k `resolve_layered` (`0xc0ffee01`) | load **0.065s** / resolve **10.620s**, same 10-package selection as SWI / D51 |

Index effect: the 5k catalog is far above `index_threshold(64)`, so
`resolve_layered` wraps `icat/3` (cannot toggle the frozen fact from
owned files). D51 pre-index Go B3 was **11.0s** linear scans; with-index
Go is **10.62s** (~1.04×). wamjs saw **9.2×** on the same G1 trees —
Go's interpreted index build (~45 WAM instructions/row × 5k) almost
cancels the lookup win. Selection matches the historical SWI 10-package
set, so the index is semantically active.

See `examples/pkg_resolver/go/README.md`.

## Path forward

Expand Down Expand Up @@ -278,3 +313,9 @@ now live on `ChoicePoint.Levels`, never in `vm.Regs`. Probe
lowered-lane defect (a lowered method that `call`s the interpreter and
then tail-recurses loses its output binding) is recorded as a residual
above — it is not the barrier and is not fixed here.
2026-09-04: **uw-resolve P3 on Go.** maplist/predsort/functor classified;
`UW_WAM_WARN_UNKNOWN`; shim speaks deb/provides/alternatives; switch
default try/retry chain; predsort A2 smash. Corpus **51/51**,
differential **2600/0**, cut **35/35**, full `test_wam_go_*.pl` sweep
green, shared-lane JS/cli_args/wamjs intact. B3 with-index **10.62s**
vs D51 pre-index **11.0s**.
53 changes: 32 additions & 21 deletions examples/pkg_resolver/go/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# uw-resolve on Go (`wam_go`)

Compile the frozen P0.5 package resolver (`../resolver.pl`) through
Compile the frozen P3 package resolver (`../resolver.pl`) through
the Go WAM backend and drive it with a JSON shim. The shim converts
catalogs/requests/env **in** and selections/explanations **out**. It
contains no resolver logic (no candidate order, no constraint
arithmetic, no layer walk).
catalogs/requests/env **in** and selections/explanations **out**,
including `{"deb":[Epoch,[[order,num],…],[…]]}`,
`{"alternatives":[…]}`, provides 3- and 4-ary rows, `catalog/10`,
and `blocked(..., providers([...]))` /
`blocked(alternatives([...]))`. It contains no resolver logic (no
candidate order, no constraint arithmetic, no layer walk).

## Build / run

Expand Down Expand Up @@ -42,6 +45,8 @@ times `resolve_layered` on `DIR/rich.jsonl` + `DIR/probe.json`.
| 7 | bagof/setof probes left `L` as `__R200` | 4-arg `begin_aggregate bagof, Y, X, ''` was dropped as `// TODO`, so `EndAggregate` was a no-op | Accept the 4th witness-register argument (empty witness list uses the 3-arg runtime) | cut-semantics p14/p15 |
| 8 | Cut suite lost extra solutions / p29 printed `1` not `ok` | Y slots are global; Proceed popped a Call Y-save that backtrack did not restore; `!/0` used the caller's `EnvFrame.CutB0` | Call pushes B0+Y; Execute rebases B0 only (LCO); Proceed pops; CPs snapshot/restore both stacks; `!/0` truncates to `PendingB0` | `tests/test_wam_go_cut_semantics.pl` (35/35) |
| 9 | B3 `resolve_layered` unified Acc with `[]` | `allocVarId` started at 1000; shim minted output vars at `Idx: 10000+i`; after ~9000 cells, `NextVarId` aliased `Bindings[10002]` with `Selection` | skip 10000–10999 in `allocVarId`; shim uses `vm.allocVarId()` | `tests/test_wam_go_varid_collision.pl` |
| 10 | P3 `provides_*` corpus rows `{fail:true}` | `SwitchOnConstantPc` default resolved to the `TryMeElse` then skipped it, so only the first classic clause of `pick_need/8` ran | fall through label-form default; keep Pc jump when the target is a try/retry head | `tests/test_wam_go_switch_default_chain.pl` |
| 11 | 40 generated `p3g*` cases `{fail:true}` | `predsort(cmp_ver)` smashed A2; Unify then compared a version to the sorted list. Corpus survived because those P3 rows had one matching version (less-func never ran) | capture A2; restore regs after each comparator; `invokeGoalOnce` drops leftover CPs | `tests/test_wam_go_maplist_predsort.pl` |

## Cut / choice-point barriers

Expand All @@ -68,21 +73,25 @@ that path.

## Benchmarks (B1–B3)

Measured on this Cloud Agent VM (SWI 9.0.4, Go 1.22.2). Dump corpus
has 39 cases (plunit `test_resolver.pl` is 38 tests; the extra dump
row is `alias_request_edge`).
Measured on this Cloud Agent VM (SWI 9.0.4, Go 1.22.2, Node v22). Dump
corpus has **51** cases (40 pre-P3 + 11 P3). Differential is 2400 `g*`
(seed `0xa5b6c7d8`) + 200 `p3g*` (seed `0xdeb00001`).

| Bench | What | Number |
|-------|------|--------|
| B1 | 39-scenario corpus, Go binary, one process (`run_corpus_go.sh`) | **0.024s wall, 39/39 vs SWI** |
| B1 | `go build` of `uwresolve` (not in B1 wall; clean rebuild) | **0.168s** |
| B2 | Differential Go leg (`run_differential_go.sh`, seed `0xa5b6c7d8`) | **8.869s, 2400 cases, 0 divergences, 0 crashes** |
| B2 | SWI oracle leg, same machine | **1.331s** |
| B3 | 5k catalog `resolve_layered` (`run_scale_go.sh`, seed `0xc0ffee01`) | Go **load 0.060s / resolve 4.604s**, 10-package selection `p1-v(1,0,0)`, `p10`, `p12`, `p2`, `p3`, `p30`, `p4`, `p5`, `p6`, `p7` |
| B1 | 51-scenario corpus, Go binary (`run_corpus_go.sh`) | **0.053s wall, 51/51 vs SWI** |
| B2 | Differential Go leg (`run_differential_go.sh`) | **17.179s, 2600 cases, 0 divergences, 0 crashes** |
| B2 | SWI oracle leg, same machine | **1.671s** |
| B3 | 5k catalog `resolve_layered` (`run_scale_go.sh`, seed `0xc0ffee01`) | Go **load 0.065s / resolve 10.620s**, 10-package selection `p1-v(1,0,0)`, `p10`, `p12`, `p2`, `p3`, `p30`, `p4`, `p5`, `p6`, `p7` |

SWI term reference on the **same** `go/.scale_out` catalog + `p30` probe
(not `run_scale_demo.sh`, which also builds the store path): **load
0.428s / resolve 0.008s**, same 10 packages.
Index: `index_threshold(64)` is a frozen fact; the 5k catalog wraps
`icat/3`. D51 pre-index Go B3 was **11.0s**; with-index is **10.62s**
(~1.04×). wamjs saw 9.2× on the same trees — Go's interpreted index
build cost nearly cancels the lookup win. Threshold itself cannot be
toggled from this directory.

`loadRichJSONL` keeps provides rows and alternatives `dep` objects (the
P3 overlay the older scale path stripped).

## Residuals

Expand All @@ -91,8 +100,10 @@ SWI term reference on the **same** `go/.scale_out` catalog + `p30` probe
for Allocate-less Y clobber; the numeric alias is unchanged. The P0.5
resolver did not hit a no-`Allocate` fact with >99 X placeholders.
- **A3**: `BuiltinExecute` covers known builtins. A builtin missed by
translation-time classification still fails silently. `call/1` is
now classified. `member/2` is `BuiltinCall` here.
translation-time classification still fails the query;
`UW_WAM_WARN_UNKNOWN=1` prints the miss (off by default). `call/1`,
`maplist/2-4`, `predsort/3`, `functor/3`, `arg/3`, `=../2` are
classified. `member/2` is `BuiltinCall` here.
- **call/1 extra solutions**: nested user goals are first-solution;
opaque `!` is correct.
- **bagof witness grouping** is not implemented (empty witness list is
Expand All @@ -101,7 +112,7 @@ SWI term reference on the **same** `go/.scale_out` catalog + `p30` probe
general heap allocator. Programs that allocate >9000 unbound cells
still wrap past the window; the shim now uses `vm.allocVarId()` so
output vars never sit in that hole.
- P0.5 resolver residuals (unchanged, frozen source): Provides/virtual,
Debian epoch/tilde, write paths, incremental stores, pkg CLI
(concurrent), per-file SFS layers.
- Do not create `cljs/` or `rust/` — concurrent rounds.
- Frozen `resolver.pl` residuals: Breaks≡Conflicts, Pre-Depends
ordering, Recommends/Suggests, multiarch, epoch display `0`, write
paths, incremental stores, pkg CLI, per-file SFS layers.
- Do not edit anything under `examples/pkg_resolver/` except `go/`.
36 changes: 22 additions & 14 deletions examples/pkg_resolver/go/atoms.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,28 @@ package wam
// Interned atom literals (compile-time deduplicated)
var (
wamAtom____0 = internAtom("[]")
wamAtom_blanket_1 = internAtom("blanket")
wamAtom_abi_anchor_2 = internAtom("abi_anchor")
wamAtom_over_frozen_3 = internAtom("over_frozen")
wamAtom_none_4 = internAtom("none")
wamAtom_classic_5 = internAtom("classic")
wamAtom_layered_6 = internAtom("layered")
wamAtom_from_catalog_7 = internAtom("from_catalog")
wamAtom_from_base_8 = internAtom("from_base")
wamAtom_any_9 = internAtom("any")
wamAtom_no_candidate_10 = internAtom("no_candidate")
wamAtom_modified_11 = internAtom("modified")
wamAtom_footprint_12 = internAtom("footprint")
wamAtom_layer_shadow_13 = internAtom("layer_shadow")
wamAtom_base_14 = internAtom("base")
wamAtom_unsatisfiable_1 = internAtom("unsatisfiable")
wamAtom_blanket_2 = internAtom("blanket")
wamAtom_abi_anchor_3 = internAtom("abi_anchor")
wamAtom_over_frozen_4 = internAtom("over_frozen")
wamAtom_t_5 = internAtom("t")
wamAtom_none_6 = internAtom("none")
wamAtom___7 = internAtom("<")
wamAtom___8 = internAtom(">")
wamAtom___9 = internAtom("=")
wamAtom_any_10 = internAtom("any")
wamAtom_layered_11 = internAtom("layered")
wamAtom_classic_12 = internAtom("classic")
wamAtom_from_catalog_13 = internAtom("from_catalog")
wamAtom_from_base_14 = internAtom("from_base")
wamAtom_unversioned_15 = internAtom("unversioned")
wamAtom_no_candidate_16 = internAtom("no_candidate")
wamAtom_modified_17 = internAtom("modified")
wamAtom_footprint_18 = internAtom("footprint")
wamAtom_layer_shadow_19 = internAtom("layer_shadow")
wamAtom_base_20 = internAtom("base")
wamAtom_is_v3_21 = internAtom("is_v3")
wamAtom_cmp_ver_22 = internAtom("cmp_ver")
)

// atomInternMap is the single source of pointer identity for atoms.
Expand Down
Loading
Loading