docs(ffi-linkage): checkpoint — Phase 4 complete, Part A done, A→B gate locked
This commit is contained in:
@@ -5,7 +5,22 @@ Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** add
|
|||||||
every commit, one step at a time per the cadence rule.
|
every commit, one step at a time per the cadence rule.
|
||||||
|
|
||||||
## Last completed step
|
## Last completed step
|
||||||
**Phase 3.1** (green) — **PHASE 3 COMPLETE.** Postfix `extern`/`export` on `#objc_class`/
|
**Phase 4** (green) — **PHASE 4 COMPLETE → PART A DONE; GATE A→B LOCKED.** Four pieces:
|
||||||
|
(1) **GATE A→B unit test** (`lower.test.zig`, `lowerSrcToIr` helper + "GATE A→B" test) —
|
||||||
|
asserts `#foreign` and `extern`/`export` lower to byte-identical printed IR for a sample
|
||||||
|
fn, data global, and Obj-C runtime class. This is the hard gate: Part B may not start
|
||||||
|
migrating `#foreign` until it's green. Verified live (negative-probe: mutating one side
|
||||||
|
fails the assertion). (2) **Diagnostic — `#foreign` + postfix conflict** (1174): prefix
|
||||||
|
`#foreign` combined with postfix `extern`/`export` on an aggregate is now a clean parse
|
||||||
|
error (was a confusing internal "compiler bug" during class synthesis). (3) **Diagnostic
|
||||||
|
— `extern`+`export` mutual exclusion** (1175): both keywords on one fn decl is a clean
|
||||||
|
error (was bare "expected ';'"). (4) **Docs**: `specs.md` + `readme.md` document the three
|
||||||
|
`extern`/`export` axes (fns, globals, aggregates) alongside `#foreign` (which stays
|
||||||
|
documented until the Part B cutover). Suite green (643 corpus / 444 unit, 0 fail).
|
||||||
|
NOTE: `extern`+`callconv` redundancy needs no diagnostic — `callconv(.c) extern` is a
|
||||||
|
harmless dup (both `.c`), and any non-`.c` callconv already errors on its own.
|
||||||
|
|
||||||
|
### Prior: Phase 3.1 (green) — **PHASE 3 COMPLETE.** Postfix `extern`/`export` on `#objc_class`/
|
||||||
`#jni_class` aggregates fully works. `parseForeignClassDecl` now parses an optional
|
`#jni_class` aggregates fully works. `parseForeignClassDecl` now parses an optional
|
||||||
`extern`/`export` modifier in the slot **between** the `("X")` directive args and the `{`
|
`extern`/`export` modifier in the slot **between** the `("X")` directive args and the `{`
|
||||||
body (`parser.zig:~1409`): `extern`→`is_foreign_eff = true` (reference an existing runtime
|
body (`parser.zig:~1409`): `extern`→`is_foreign_eff = true` (reference an existing runtime
|
||||||
@@ -47,22 +62,30 @@ data globals — bare + renamed. export: functions — bare (`f :: (…) -> R ex
|
|||||||
AND renamed (`export "csym"`); external linkage, C ABI, no ctx, force-lowered as a root.
|
AND renamed (`export "csym"`); external linkage, C ABI, no ctx, force-lowered as a root.
|
||||||
All behavior-equivalent to the matching `#foreign` form. `extern_lib` is parsed + stored
|
All behavior-equivalent to the matching `#foreign` form. `extern_lib` is parsed + stored
|
||||||
but is a *reference* only — actual linking stays the `#library`/build-flag axis.
|
but is a *reference* only — actual linking stays the `#library`/build-flag axis.
|
||||||
Aggregates NOT started (Phase 3). Part B `foreign` footprint to purge: 643 lines / ~57
|
**Aggregates DONE (Phase 3)**: postfix `extern`/`export` on `#objc_class`/`#jni_class`
|
||||||
identifiers in `src/` + 28 doc lines. End-state invariant: **zero `foreign`** (Phase 9.4
|
(reference vs define+register). **Interplay/diagnostics/docs DONE (Phase 4)** + the
|
||||||
gate). Examples: 1223 (extern bare fn), 1224 (extern fn rename), 1225 (extern bare
|
**A→B GATE IS LOCKED** (`#foreign` ≡ `extern`/`export` IR for fn/global/class). **PART A
|
||||||
global), 1226 (export bare fn, AOT), 1227 (export fn rename, AOT).
|
COMPLETE.** Part B `foreign` footprint to purge: 643 lines / ~57 identifiers in `src/` +
|
||||||
|
~28 doc lines. End-state invariant: **zero `foreign`** (Phase 9.4 gate). Examples: 1223
|
||||||
|
(extern bare fn), 1224 (extern fn rename), 1225 (extern bare global), 1226 (export bare fn,
|
||||||
|
AOT), 1227 (export fn rename, AOT), 1348 (objc extern class), 1349 (objc export class), 1426
|
||||||
|
(jni extern class), 1174/1175 (interplay diagnostics).
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
**Phase 4 — interplay, diagnostics, docs (+ the A→B gate).** (a) Diagnostics for combining
|
**PART B — Phase 5 (`#foreign` becomes an alias for `extern`).** Part A is complete and
|
||||||
the surfaces: reject `extern`+`export` together; decide/handle prefix `#foreign` **and**
|
the A→B gate is locked, so migration can begin. Phase 5.0 (`lock`): route the four
|
||||||
postfix `extern`/`export` on the same aggregate (today the postfix silently overrides
|
`#foreign` parser paths (`parser.zig:316,425,1305,1970`) to build the **same** extern-named
|
||||||
`is_foreign` — Phase 4 should reject the redundant/contradictory combo, mirroring the fn
|
AST as `extern`/`export` — suite green, all snapshots unchanged. Phase 5.1 (`lock`): unit
|
||||||
path). (b) `extern`+`callconv` stacking/redundancy on fns. (c) docs — `specs.md`/`readme.md`
|
test that `#foreign` and `extern` produce identical IR (the gate already covers this — extend
|
||||||
document the three `extern`/`export` axes (fns, globals, aggregates); `#foreign` stays
|
or reuse `lowerSrcToIr`). Then Phases 6–7 migrate stdlib + examples (empty snapshot diff per
|
||||||
documented until the Part B cutover. (d) **GATE A→B (hard):** unit test asserting `#foreign`
|
batch), Phase 8 cutover (hard-reject `#foreign`), Phase 9 total `foreign` purge.
|
||||||
and `extern`/`export` lower to identical IR for a sample fn / global / **class** — lock
|
|
||||||
before any Part B migration. Also pick up the two **Deferred** items below at this gate. (interplay/diagnostics/docs + the A→B gate: unit test that
|
**⚠ CONFIRM BEFORE PART B (Open decisions 5 & 6):** runtime-class rename target
|
||||||
`#foreign` and `extern`/`export` lower to identical IR) before Part B migration.
|
(`Runtime*Class*` recommended vs `Extern*Class*`) and the historical carve-out (keep
|
||||||
|
`issues/*.md` provenance, gate live tree only — recommended). These decide Phase 9 renames;
|
||||||
|
the plan says confirm before Phase 9, but worth raising with the user before sinking Part B
|
||||||
|
effort. **Also pick up the two Deferred items below at the start of Part B** (the
|
||||||
|
visibility-gate equivalence in particular needs a cross-module example).
|
||||||
|
|
||||||
**FUTURE MILESTONE — C→sx-by-name in JIT (`sx run`).** Investigated this session
|
**FUTURE MILESTONE — C→sx-by-name in JIT (`sx run`).** Investigated this session
|
||||||
(user-requested spike, RESOLVED feasible-but-blocked). Adding the C `#source` objects
|
(user-requested spike, RESOLVED feasible-but-blocked). Adding the C `#source` objects
|
||||||
@@ -78,9 +101,9 @@ examples crashed in the spike. A real impl needs a C++ shim in `llvm_shim.c`
|
|||||||
milestone, NOT Phase 2/3 scope. The AOT `.aot`-marker corpus mode is the pragmatic test
|
milestone, NOT Phase 2/3 scope. The AOT `.aot`-marker corpus mode is the pragmatic test
|
||||||
path and works today. Spike fully reverted (target.zig/main.zig at HEAD).
|
path and works today. Spike fully reverted (target.zig/main.zig at HEAD).
|
||||||
|
|
||||||
**Deferred (do in Phase 4):** (a) docs — `specs.md`/`readme.md` document
|
**Deferred (carry into Part B):** (a) ~~docs~~ — DONE in Phase 4 (`specs.md`/`readme.md`
|
||||||
`extern`/`export` (the plan defers docs to Phase 4; `#foreign` stays documented until
|
document `extern`/`export`; `#foreign` stays until the Part B cutover); (b) visibility-gate
|
||||||
the Part B cutover); (b) visibility-gate equivalence — bare `extern` (no `extern_lib`)
|
equivalence — bare `extern` (no `extern_lib`)
|
||||||
is currently unconditionally visible via the `c_import_bare` gate
|
is currently unconditionally visible via the `c_import_bare` gate
|
||||||
(`decl.zig:~2241`, `fd.body.data != .foreign_expr → return true`), whereas a lib-less
|
(`decl.zig:~2241`, `fd.body.data != .foreign_expr → return true`), whereas a lib-less
|
||||||
`#foreign` is policed by `visibleOverEdges`. Single-file examples don't exercise this;
|
`#foreign` is policed by `visibleOverEdges`. Single-file examples don't exercise this;
|
||||||
@@ -162,6 +185,24 @@ historical carve-out — keep `issues/*.md` provenance, gate the live tree only.
|
|||||||
the cadence rule). Suite green (641/443). `lock` commit. (Note: `-Dupdate-goldens`
|
the cadence rule). Suite green (641/443). `lock` commit. (Note: `-Dupdate-goldens`
|
||||||
newline-normalizes empty stderr → reverted unrelated 1226/1227 churn, kept new stderr
|
newline-normalizes empty stderr → reverted unrelated 1226/1227 churn, kept new stderr
|
||||||
0-byte per repo convention; runner normalizes both.)
|
0-byte per repo convention; runner normalizes both.)
|
||||||
|
- (4.gate) **GATE A→B** — added `lowerSrcToIr` helper + "GATE A→B" test to `lower.test.zig`:
|
||||||
|
`#foreign` ≡ `extern`/`export` byte-identical printed IR for fn / global / Obj-C class.
|
||||||
|
Verified live via negative-probe (mutate one side → assertion fails). Behavior-lock; the
|
||||||
|
equivalence was prototyped first with `sx ir` (LLVM IR byte-identical for all three).
|
||||||
|
Suite green (641/444). `test` commit.
|
||||||
|
- (4.diag1) Added `examples/1174-diagnostics-foreign-postfix-conflict.sx` — prefix `#foreign`
|
||||||
|
+ postfix `export` on an aggregate previously surfaced a confusing internal
|
||||||
|
"emitObjcDefinedClassAllocImp … compiler bug". `xfail` (golden = clean message) → `green`:
|
||||||
|
`parseForeignClassDecl` rejects the combo at the postfix keyword (`failFmt`). Suite green.
|
||||||
|
- (4.docs) `specs.md` (new "`extern`/`export` linkage keywords" subsection after the
|
||||||
|
`#foreign` FFI docs) + `readme.md` (C Interop section) document the three axes. `docs` commit.
|
||||||
|
- (4.diag2) Added `examples/1175-diagnostics-extern-export-conflict.sx` — `extern export` on
|
||||||
|
one fn decl previously gave bare "expected ';'". `xfail` (golden = clean message) → `green`:
|
||||||
|
`parseFnDecl` rejects a second linkage keyword after `parseOptionalExternExport`. Suite
|
||||||
|
green (643/444). **PHASE 4 COMPLETE → PART A DONE.** (Workflow note: `-Dupdate-goldens`
|
||||||
|
keeps newline-normalizing the 5 empty-stderr files [1226/1227/1348/1349/1426] — revert
|
||||||
|
that churn after each regen; a flaky `0712-sha256-streaming` timeout appears only under
|
||||||
|
concurrent `zig build` load, not a real failure.)
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
- **Workflow hazard (1.2):** an editor format-on-save (or `zig fmt`) clobbered the
|
- **Workflow hazard (1.2):** an editor format-on-save (or `zig fmt`) clobbered the
|
||||||
|
|||||||
Reference in New Issue
Block a user