Skip to content

Commit e9c05aa

Browse files
fix(coproc): decouple from Axiom.jl (own seam) + §4.8 path-3-disproved doc (#297)
Two follow-ups to #296 (which merged before these were committed). **fix(coproc): decouple from Axiom.jl.** Per owner directive: Axiom.jl was the design *model* (capability classes + linear buffers + thin FFI seam, full coverage), **not** a dependency. Removes all 'forward to / surface / import / co-own Axiom' framing from `Coproc.eph`, the Zig shim (`TODO(axiom)`→`TODO(backend)`, drop Julia C-embedding), and the README — ephapax owns its own seam, dispatching to a native backend it chooses. Code unchanged (always a generic FFI boundary; only the narrative was wrong). Adds `just build-coproc`/`test-coproc`. Documents honestly (README + gaps **G6**) that the typed `Coproc` API is **not yet consumable** — `import Coproc; Coproc.upload(…)` neither parses (qualified `Coproc.Tensor`) nor resolves (import exposes no members): the keystone gap stranding the whole stdlib. Seam reachable today only via raw `__ffi` (42-vs-0 proof). **docs(formal): §4.8 path (3) disproved.** Records that the owner-approved leaf-rule strengthening was implemented + coqc-disproved (false at type-erasing eliminators, `EDrop(EVar j)`); the real fork is effect typing (A′). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 40b71de commit e9c05aa

6 files changed

Lines changed: 186 additions & 74 deletions

File tree

Justfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ build:
1515
build-wasm:
1616
cargo build --target wasm32-unknown-unknown
1717

18+
# Build ephapax's own coprocessor seam (libephapax_coproc.so).
19+
# This is the native C-ABI seam ephapax reaches via __ffi (-L); it is NOT
20+
# coupled to any external framework. NOTE: the typed `Coproc` stdlib API is
21+
# not yet consumable by .eph programs (no module-import / qualified-access
22+
# in the v2 grammar — see docs/v2-grammar-and-codegen-gaps-2026-06-16.adoc);
23+
# today the seam is reachable only via raw `__ffi("eph_coproc_*", …)`.
24+
build-coproc:
25+
#!/usr/bin/env bash
26+
set -euo pipefail
27+
command -v zig >/dev/null 2>&1 || { echo "zig not found"; exit 1; }
28+
zig build-lib -dynamic -O ReleaseSafe tools/coproc/ephapax_coproc.zig -femit-bin=libephapax_coproc.so
29+
echo "built libephapax_coproc.so (run: ephapax run prog.eph -L libephapax_coproc.so)"
30+
31+
# Run the coprocessor seam's own unit tests (Zig).
32+
test-coproc:
33+
zig test tools/coproc/ephapax_coproc.zig
34+
1835
# Structurally validate emitted wasm for the compilable fixture corpus.
1936
# Compiles each fixture and runs `wasm-tools validate`, failing on any
2037
# structurally invalid module. Authoritative CLI twin of the in-process

docs/v2-grammar-and-codegen-gaps-2026-06-16.adoc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,30 @@ the symbols or fail with a clean "FFI unsupported in the wasm target" error
110110
writing a broken `.wasm`. This is tracked as codegen debt; the coprocessor
111111
surface is unaffected because it dispatches via the interpreter's `__ffi`
112112
path, not wasm.
113+
114+
== G6 — no working module-import + qualified-member access (THE keystone)
115+
116+
A program cannot consume another module's API:
117+
118+
----
119+
$ printf 'import Coproc\nfn t(): I64 =\n Coproc.available(Coproc.Tensor)\n' > c.eph; ephapax check c.eph
120+
Parse error: Unexpected atom expression: construct_expr # `Coproc.Tensor` (qualified) won't parse
121+
122+
$ printf 'import Coproc\nfn t(): I64 =\n available(Tensor)\n' > c.eph; ephapax check c.eph
123+
desugar error: unknown constructor `Tensor` # `import` doesn't bring members into scope
124+
----
125+
126+
So `import M; M.f(...)` neither parses (qualified `M.member` is rejected as a
127+
`construct_expr`) nor resolves (an `import` does not make `M`'s types /
128+
constructors / functions visible, even unqualified). This is the **keystone
129+
gap**: every stdlib module (`Region`, `Stream`, `Transactions`, `Prelude`,
130+
`Coproc`, …) is a typed surface that **nothing can consume**. It is why the
131+
coprocessor seam is reachable today only through raw `__ffi("eph_coproc_*",
132+
…)` rather than the typed `Coproc.upload`/`dispatch`/… API.
133+
134+
*This is the single highest-leverage v2 toolchain item:* module-import +
135+
qualified-member access (the import resolver in
136+
`src/ephapax-cli/src/import_resolver.rs` already loads the import graph for
137+
multi-file `compile`, but the surface parser/desugarer does not expose
138+
imported members). Landing it unblocks consumption of the entire stdlib at
139+
once.

formal/ESCALATION-4.8-leaf-rule-region-liveness.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,54 @@
66
**Status:** OPEN — needs an owner design decision before any further L1
77
preservation proof work. Raised 2026-06-16.
88

9+
> ## ⚠️ UPDATE 2026-06-16 — path (3) **proven insufficient**; the real fork is effect typing
10+
>
11+
> An implementation attempt of path (3) below (strengthen `T_Var_*_L1` with
12+
> `In r (free_regions T) -> In r R`) was carried to a coqc-verified
13+
> intermediate and **disproved**. Path (3) closes the variable leaf and every
14+
> *type-preserving* constructor, but `region_shrink_preserves_typing_l1_gen_m`
15+
> becomes a **false statement** at *type-erasing eliminators*.
16+
>
17+
> **Counterexample (coqc-checked shape).** `EDrop (EVar j)` with
18+
> `j : TString rr`, at `R = [rr]`:
19+
> - types via `T_Drop_L1` (TString is linear) + strengthened `T_Var` (which
20+
> forces `In rr R`, satisfied by `In rr [rr]`); output type is `TUnit`;
21+
> - `expr_strictly_free_of_region rr (EDrop (EVar j)) = True` (no syntactic `rr`);
22+
> - `~ In rr (free_regions TUnit)` holds trivially (the Tofte-Talpin premise
23+
> path (3)/region_shrink can supply);
24+
> - **yet** shrinking `rr` to `R = []` leaves the inner `EVar j : TString rr`
25+
> untypable (`In rr [] = False`). So `region_shrink` is *false* here.
26+
>
27+
> The same shape hides inside a **lambda value** whose body uses-and-drops a
28+
> captured `TString rr` variable, so an `is_value` restriction does not rescue
29+
> it. **Root cause:** a closure / eliminator can depend on a region *without
30+
> exposing it in its result type*; the `~ In r (free_regions T)` premise
31+
> constrains only the *result* type, which these rules erase. This same gap
32+
> blocks all four targets (region_liveness's ~13 `subst_typing_gen_l1_m` call
33+
> sites thread the *substituted location's* region, which need not appear in
34+
> the sub-expression's output type; step_pop's 9 admits are the same "inner
35+
> step pops a region free in the sibling/outer type").
36+
>
37+
> **Therefore the decision is now sharper than (A)/(B)/(C) below:**
38+
> - **(A′) Adopt effect typing** — deprecate the bare-`TFun` lambda rules
39+
> (`T_Lam_L1_Linear`/`_Affine`) and keep only the effect-typed
40+
> `T_Lam_L1_*_Eff` (`TFunEff … R_in R_out`), whose `free_regions` already
41+
> includes `R_in ++ R_out`. Then every region dependency is type-visible and
42+
> the threading composes through eliminators. This is path (1), already
43+
> half-built (the TFunEff "live line"). **Recommended — but it changes the
44+
> accepted surface language: it rejects region-capturing bare-`TFun`
45+
> closures. That rejection is the owner's call.**
46+
> - **(B′) A context-region-freedom precondition** on `region_shrink`
47+
> ("no variable position `e` reads carries `rr` in its type"), a new
48+
> predicate over `e × G` threaded from call sites. Heavier; no surface change.
49+
> - **(C′) Leave the four admits** — but note the path-(3) `T_Var` premise
50+
> **cannot coexist with a green build** without (A′)/(B′), so status-quo means
51+
> *also reverting* the `T_Var` strengthening (done — `formal/` is back to
52+
> clean green-with-one-axiom).
53+
>
54+
> No code from the attempt remains; `formal/` is clean green-with-admits. The
55+
> recommendation below (plain "(A)") is **superseded** by (A′) above.
56+
957
**Why this is an escalation and not a patch.** `CLAUDE.md`
1058
*Anti-pattern detector* lists "strengthened lemma signatures within the
1159
current judgment shape" and "sibling-region-disjointness side conditions"

stdlib/Coproc.eph

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,40 @@
44
//
55
// Ephapax Coprocessor API — a THIN typed dispatch boundary (v2 grammar).
66
//
7-
// This module implements NO coprocessor kernels. The ten capability classes
8-
// surface the EXISTING coprocessor suite in `Axiom.jl` (Julia) — the
9-
// DSP/Crypto/Math/PPU/GPU/VPU/TPU/QPU/NPU/FPGA backends behind Axiom's
10-
// `backend_coprocessor_*` generics. ephapax holds only a typed surface plus
11-
// a substructural safety gate; routing, fallback, and benchmarking stay in
12-
// Axiom (boundary kept clean — no porting).
7+
// This module implements NO coprocessor kernels and depends on NO external
8+
// framework. It is ephapax's OWN coprocessor dispatch surface. Its DESIGN —
9+
// typed capability classes, a linear device-buffer lifecycle, and a thin
10+
// C-ABI seam — follows the pattern Axiom.jl established (which demonstrated
11+
// this shape across the full range of coprocessor types). ephapax models
12+
// that pattern; it does NOT import, depend on, co-own, or forward to Axiom.
13+
// ephapax owns its own seam end-to-end.
1314
//
14-
// The single shared seam is a thin C ABI: `libephapax_coproc.so` (see
15+
// The shared seam is a thin C ABI: `libephapax_coproc.so` (see
1516
// `tools/coproc/ephapax_coproc.zig`) exports flat `eph_coproc_*` symbols
16-
// that forward to Axiom, mirroring how Axiom itself links Zig. ephapax
17-
// reaches them through its ordinary `__ffi(...)` path (load with
18-
// `-L libephapax_coproc.so`). When the library is absent each call falls
19-
// through to ephapax's `[ffi:stub]` -> 0 path, so the surface stays usable
20-
// with no coprocessor present.
17+
// that dispatch to whatever native coprocessor backend is linked behind
18+
// them. ephapax reaches them through its ordinary `__ffi(...)` path (load
19+
// with `-L libephapax_coproc.so`). When the library is absent each call
20+
// falls through to ephapax's `[ffi:stub]` -> 0 path, so the surface stays
21+
// usable with no coprocessor present.
2122

2223
module Coproc
2324

2425
// ── Capabilities ──────────────────────────────────────────────────────────
2526
//
26-
// The ten coprocessor classes, each mapping 1:1 onto an Axiom.jl
27-
// CoprocessorBackend. The declaration order documents the intent; the wire
28-
// contract with the Zig shim is made explicit by `cap_tag` below.
27+
// The ten coprocessor classes (the coverage this design targets). The
28+
// declaration order documents the intent; the wire contract with the Zig
29+
// shim is made explicit by `cap_tag` below.
2930
pub data Capability =
30-
| Audio // -> Axiom DSPBackend (Audio PU / digital signal processing)
31-
| Crypto // -> Axiom CryptoBackend
32-
| Maths // -> Axiom MathBackend
33-
| Physics // -> Axiom PPUBackend (physics processing unit)
34-
| Gpu // -> Axiom CUDA / Metal / ROCm
35-
| Vector // -> Axiom VPUBackend (vector processing unit)
36-
| Tensor // -> Axiom TPUBackend (tensor processing unit)
37-
| Quantum // -> Axiom QPUBackend (quantum processing unit)
38-
| Io // -> Axiom NPUBackend / host I/O path
39-
| Fpga // -> Axiom FPGABackend
31+
| Audio // digital signal processing (audio PU)
32+
| Crypto // cryptographic acceleration
33+
| Maths // general numeric / math acceleration
34+
| Physics // physics processing unit
35+
| Gpu // general-purpose GPU
36+
| Vector // vector processing unit
37+
| Tensor // tensor processing unit
38+
| Quantum // quantum processing unit
39+
| Io // I/O / host offload path
40+
| Fpga // field-programmable gate array
4041

4142
// An opaque handle to a buffer living on a coprocessor. LINEAR: once
4243
// uploaded it must be consumed EXACTLY ONCE — by `download` (pull the

tools/coproc/README.adoc

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ toc::[]
77

88
== What this is
99

10-
A *thin typed dispatch boundary* that lets ephapax use the **existing**
11-
coprocessor suite in `Axiom.jl` (Julia) — the ten DSP/Crypto/Math/PPU/
12-
GPU/VPU/TPU/QPU/NPU/FPGA backends behind Axiom's `backend_coprocessor_*`
13-
generics — **without porting any kernel into ephapax**.
10+
ephapax's **own** *thin typed dispatch boundary* for coprocessors — ten
11+
capability classes (Audio/Crypto/Maths/Physics/GPU/Vector/Tensor/Quantum/
12+
IO/FPGA) reached over a single C-ABI seam, with the language's linear types
13+
guarding the device-buffer lifecycle.
1414

15-
The boundary is deliberately clean (no framework internals cross into the
16-
language):
15+
The DESIGN follows the pattern Axiom.jl demonstrated (capability classes +
16+
linear buffers + a thin FFI seam, covering the full range of coprocessor
17+
types). ephapax *models* that pattern — it does **not** import, depend on,
18+
co-own, or forward to Axiom. The seam is ephapax's own, and the boundary is
19+
deliberately clean (no framework internals cross into the language):
1720

1821
[cols="1,3"]
1922
|===
2023
| `stdlib/Coproc.eph` | The typed ephapax surface: a `Capability` sum type (the ten classes), a `cap_tag` wire-tag map, and a LINEAR `CoprocBuf` device-buffer handle with an `upload → dispatch* → (download \| release)` lifecycle. Type-checks under `ephapax check`.
21-
| `tools/coproc/ephapax_coproc.zig` | The single shared seam: a Zig shared library exporting a flat C ABI (`eph_coproc_*`, all `i64 (...) -> i64`). In a full build it forwards to Axiom via the Julia C-embedding API; the in-tree build is a STUB (reports unavailable, with one loopback for the seam test). Mirrors how Axiom itself links Zig.
24+
| `tools/coproc/ephapax_coproc.zig` | The single C-ABI seam: a Zig shared library exporting flat `eph_coproc_*` symbols (`i64 (...) -> i64`) that dispatch to whatever native coprocessor backend is linked behind them. The in-tree build is a STUB (reports unavailable, with one loopback for the seam test). No external framework, no Julia, no Axiom runtime.
2225
| ephapax `__ffi` path | ephapax reaches the seam through its ordinary foreign-call mechanism — no new language feature. Load with `-L libephapax_coproc.so`.
2326
|===
2427

@@ -55,14 +58,30 @@ returns `42` with the shim linked, and falls through to ephapax's built-in
5558
`[ffi:stub] -> 0` when it is not — proving the `__ffi` path reaches this
5659
shim.
5760

58-
== Wiring the real Axiom backend
61+
== Wiring status (honest)
5962

60-
The `// TODO(axiom)` markers in `ephapax_coproc.zig` are the exact forwarder
61-
points. Each `eph_coproc_*` symbol selects the Axiom `CoprocessorBackend`
62-
struct from its capability tag and `jl_call`s the matching
63-
`Axiom.backend_coprocessor_*` generic (`Axiom.jl/src/backends/abstract.jl`),
64-
copying nothing out of Axiom. Axiom keeps all routing, fallback, and
65-
benchmarking; ephapax keeps the typed surface + the linear gate.
63+
*WIRED:* the seam build target (`just build-coproc` / `just test-coproc`),
64+
and the raw `__ffi` path reaching it (the 42-vs-0 proof above).
65+
66+
*NOT YET WIRED:* no `.eph` program can use the *typed* `Coproc` API. The v2
67+
toolchain has no working module-import + qualified-member access, so
68+
`import Coproc; Coproc.upload(…)` neither parses (`Coproc.Tensor` →
69+
"construct_expr") nor resolves (`import` does not bring members into scope →
70+
"unknown constructor"). This is the **same keystone gap that strands the
71+
whole stdlib** (Region/Stream/… are equally un-consumable) — see
72+
`docs/v2-grammar-and-codegen-gaps-2026-06-16.adoc`. Until module-import lands,
73+
the seam is reachable only through raw `__ffi("eph_coproc_*", …)`. The typed
74+
surface is a *foundation*, not yet a consumable feature.
75+
76+
== Wiring a real backend
77+
78+
The `// TODO(backend)` markers in `ephapax_coproc.zig` are the exact
79+
extension points. Each `eph_coproc_*` symbol selects a coprocessor unit from
80+
its capability tag and dispatches to the native backend's kernel for that
81+
op. ephapax keeps the typed surface + the linear gate; the backend behind
82+
the seam owns routing, fallback, and kernels. The backend is ephapax's to
83+
choose (native, vendor SDK, or a separate process) — it is **not** part of,
84+
nor coupled to, any external framework.
6685

6786
An optional `CoprocResult` round-trip proof for the status contract can be
6887
added to `src/abi/Ephapax/ABI/Foreign.idr` (mirroring `CompileResult`),

0 commit comments

Comments
 (0)