diff --git a/current/CHECKPOINT-EXTERN-EXPORT.md b/current/CHECKPOINT-EXTERN-EXPORT.md index 7b2c004..1a61bff 100644 --- a/current/CHECKPOINT-EXTERN-EXPORT.md +++ b/current/CHECKPOINT-EXTERN-EXPORT.md @@ -5,43 +5,61 @@ Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** add every commit, one step at a time per the cadence rule. ## Last completed step -**Phase 1.2d** (green) — **PHASE 1 COMPLETE.** `extern` data globals now parse + lower: -added a `kw_extern` branch in the var-decl-with-type-annotation path (`parser.zig:~451`, -beside `#foreign`) parsing `[LIB] ["csym"]` into `is_extern`/`extern_lib`/`extern_name` -(+ updated the trailing diagnostic to list `extern`); `registerTopLevelGlobal` now uses -`extern_name orelse foreign_name orelse name` for the C symbol and sets `is_extern = -is_foreign or is_extern`; `globalInitValue` returns null (no init) for extern too. -Example **1225 green** — `__stdinp : *void extern;` lowers to `@__stdinp = external -global ptr` and `@__stdinp` reads non-null. Full suite green (636 corpus / 443 unit, 0 -fail). Kickoff exit criteria met: suite green; extern libc fn + global bindings run; -`#foreign` unregressed (all its examples pass → snapshots unchanged). +**Phase 2.2** (green) — **PHASE 2 COMPLETE.** `export` (define + expose) fully works: +external linkage + C ABI + no sx ctx + force-lowered root + optional `"csym"` rename. +All four export-gap conditions filled in `decl.zig`: (i) `.external` linkage for +`extern_export == .export_` on both define paths (`lowerFunctionBodyInto`, +`lowerFunction`); (ii) C-ABI promotion on the define paths + `declareFunction` stub cc; +(iv) `funcWantsImplicitCtx` returns false for any non-`.none` modifier; **force-lower**: +`export` fns are lowering roots in `lowerMainAndComptime` (else an uncalled export fn +stays a bodiless `declare`); (iii) `export … "csym"` declares the stub under the C name ++ `lazyLowerFunction` promotes the body into it via `foreign_name_map`. Examples **1226** +(bare export, C calls `sx_square` → 37/82) + **1227** (`export "triple_c"`, C calls +`triple_c` → 22) green via the new **AOT corpus mode**. Suite green (638 corpus / 443 +unit, 0 fail). + +**AOT corpus mode + run_examples.sh retired.** C→sx-by-name can't link under the +corpus's `sx run` JIT mode (a JIT-resident symbol is invisible to a dlopen'd C dylib's +flat-namespace lookup), so an `expected/.aot` marker switches an example to a +`sx build` + execute flow. The standalone `tests/run_examples.sh` was deleted — +`zig build test` is now the sole corpus runner (verify-step.sh + CLAUDE.md updated). ## Current state Syntax: bare `extern`/`export`, postfix after `callconv(.c)`, `extern ⇒ callconv(.c)`. **Decision 4 revised** (user 2026-06-14): `extern` carries an optional `LIB`+`"csym"` axis (`extern_lib`/`extern_name`) like `#foreign`; the `#library` decl + build-flag -linking stays separate. **`extern` IS FULLY WORKING** (PHASE 1 DONE): functions — -bare (`f :: (…) -> R extern;`) AND renamed (`extern [LIB] "csym"`); data globals — -bare (`g : T extern;`) AND renamed. All behavior-equivalent to the matching `#foreign` -form (external linkage, C ABI, no sx ctx). `extern_lib` is parsed + stored but is a -*reference* only — actual linking stays the `#library`/build-flag axis (same as -`#foreign`'s lib ref). `export` NOT started (Phase 2). Aggregates NOT started (Phase 3). -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 (bare -fn), 1224 (fn rename), 1225 (bare global). +linking stays separate. **`extern` (PHASE 1) + `export` (PHASE 2) FULLY WORKING.** +extern: functions — bare (`f :: (…) -> R extern;`) AND renamed (`extern [LIB] "csym"`); +data globals — bare + renamed. export: functions — bare (`f :: (…) -> R export {…}`) +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 +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 +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). ## Next step -**SESSION STOP** — kickoff scope was Phases 0–1 only; Phase 1 is complete. Do NOT -start Phase 2 here. Next session picks up **Phase 2 — `export`** (define + expose): -fills the four export-gap conditions in `decl.zig` (all on the *define* path, not -`declareExtern`): (i) force `.external` linkage when `extern_export == .export_` -(`:2382`/`:2514`); (ii) promote to C ABI (`:2110`/the `lowerFunction` cc at `:2522`); -(iii) symbol-name override via `export "csym"` (already parsed into `extern_name` — -just consume on the define path); (iv) ctx already suppressed for `.export_`? NO — -`funcWantsImplicitCtx` currently suppresses only `.extern_`; broaden to `!= .none` -(or add `.export_`). Start with an xfail multi-file test: an `export fn` called from a -companion `.c` caller. Then Phase 3 (aggregates), Phase 4 (interplay/diagnostics/docs -+ the A→B gate: unit test that `#foreign` and `extern` lower to identical IR). +**Phase 3 — aggregates** (objc / jni runtime classes): `#objc_class("X") extern { … }` +(import) + `… export { … }` (define) parse alongside legacy `#foreign #objc_class` +(`parser.zig` `tryParseForeignClassPrefix`/`parseForeignClassDecl`); map postfix +`extern`→reference, `export`→define+register (`objc_class.zig`); per-runtime tests +(objc, jni). Then Phase 4 (interplay/diagnostics/docs + the A→B gate: unit test that +`#foreign` and `extern`/`export` lower to identical IR) before Part B migration. + +**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 +directly into the ORC JITDylib (`LLVMOrcLLJITAddObjectFile`) instead of dlopen'ing a +dylib makes C↔sx cross-references resolve both ways in one link domain — proven: a +~20-line spike ran 1226 via `sx run` (37/82) and all 13 existing `#source` FFI examples +still passed. BLOCKER: C objects using `_Thread_local` (the return-trace runtime +`sx_trace.c`) SIGABRT under JITLink — MachO thread-local-variable handling needs the ORC +`MachOPlatform` set up (the bare `LLVMOrcCreateLLJIT` default doesn't), and C +constructors/`__mod_init_func` won't run without ORC initializer support. 42 `errors-*` +examples crashed in the spike. A real impl needs a C++ shim in `llvm_shim.c` +(`LLJITBuilder().setObjectLinkingLayerCreator(...)` + `MachOPlatform::Create`) — its own +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). **Deferred (do in Phase 4):** (a) docs — `specs.md`/`readme.md` document `extern`/`export` (the plan defers docs to Phase 4; `#foreign` stays documented until @@ -92,6 +110,27 @@ historical carve-out — keep `issues/*.md` provenance, gate the live tree only. `is_extern`/`extern_lib`/`extern_name`) + `registerTopLevelGlobal`/`globalInitValue` consume `is_extern`. 1225 green (`@__stdinp = external global ptr`). Suite green (636/443). `green` commit. **PHASE 1 COMPLETE** — `extern` fns + globals fully work. +- (JIT spike) User-requested feasibility investigation of C→sx-by-name in `sx run` + (JIT). Verdict: feasible via `LLVMOrcLLJITAddObjectFile` (C objects into the ORC + JITDylib) — proven by a throwaway spike — but blocked by JITLink MachO TLV handling + (`sx_trace.c`'s `_Thread_local` SIGABRTs without the ORC `MachOPlatform`). Own future + milestone (see Next step). Spike reverted; no commit. +- (2.0) Added the **AOT corpus mode** (`expected/.aot` → `sx build` + execute) to + `corpus_run.test.zig` + retired `tests/run_examples.sh` (verify-step.sh/CLAUDE.md + updated) + `examples/1226-ffi-export-fn.{sx,c,h}` (C calls `sx_square` back). RED (AOT + link fails: `_sx_square` undefined — export not lowered). `xfail`; 2.1 greens it. +- (2.1) Filled export gaps i/ii/iv in `decl.zig` (`.external` linkage + `.c` cc on both + define paths; `funcWantsImplicitCtx` false for any non-`.none` modifier) + force-lower + export fns as roots in `lowerMainAndComptime`. 1226 green via AOT (37/82). Suite green + (637/443). `green` commit. +- (2.2a) Added `examples/1227-ffi-export-fn-rename.sx` (`export "triple_c"`, C calls + `triple_c`). RED (define path emits `@sx_triple`, ignores `extern_name` → C ref + undefined). `xfail`; 2.2b greens it. +- (2.2b) `declareFunction` rename branch fires for `export` (stub under C name + + sx→C in `foreign_name_map`); `lazyLowerFunction` resolves the stub by that C name so + the body promotes into the C-named function (`define @triple_c`). sx-side call sites + resolve via the same map (probe: 5*5→25). 1227 green (22); 1226 unregressed. Suite + green (638/443). `green` commit. **PHASE 2 COMPLETE** — `export` fully works. ## Known issues - **Workflow hazard (1.2):** an editor format-on-save (or `zig fmt`) clobbered the