Route a bare 'extern' fn declare-only, exactly like a lib-less #foreign import. Six edits in decl.zig, each mirroring an existing foreign_expr guard so the empty-block placeholder body is never lowered: 1. funcWantsImplicitCtx: suppress the implicit __sx_ctx for .extern_ 2. declareFunction: add is_extern_decl 3. ...and include it in the C-ABI calling-convention promotion 4. lazyLowerFunction: .extern_ -> declareFunction (declare-only) 5. lowerFunction: .extern_ in the declare-only guard 6. lowerFunctionBodyInto: never promote/lower an extern stub examples/1223 now green: 'extern' abs lowers to 'declare i32 @abs(i32)' (external linkage, C ABI, no ctx param) and the call resolves against the default-linked libc -> abs(-7)=7, abs(42)=42. The 1.0b hand-authored snapshot matched byte-exact (no regen). Suite green (634 corpus / 443 unit). green commit (makes the 1.0b xfail pass; adds no new test).
77 lines
4.8 KiB
Markdown
77 lines
4.8 KiB
Markdown
# sx `extern`/`export` + `#foreign` retirement — Checkpoint (FFI-linkage stream)
|
|
|
|
Companion to `current/PLAN-EXTERN-EXPORT.md` — one merged plan: **Part A** adds
|
|
`extern`/`export`, **Part B** migrates `#foreign` and purges `foreign`. Update after
|
|
every commit, one step at a time per the cadence rule.
|
|
|
|
## Last completed step
|
|
**Phase 1.1** (green) — wired extern fn lowering in `decl.zig`; example **1223 now
|
|
green**, full suite green (634 corpus / 443 unit, 0 fail). A bare `extern` fn lowers
|
|
exactly like a lib-less `#foreign` import: `declare i32 @abs(i32) #0` — external
|
|
linkage, C ABI, NO `__sx_ctx` param; calls emit `call i32 @abs(i32 -7)` and resolve
|
|
against the default-linked libc. Six edits, all routing `extern` declare-only
|
|
(mirroring the `foreign_expr` guards): (1) `funcWantsImplicitCtx` suppresses ctx for
|
|
`.extern_`; (2) `declareFunction` adds `is_extern_decl` and (3) includes it in the
|
|
C-ABI promotion; (4) `lazyLowerFunction` routes `.extern_`→declare-only; (5)
|
|
`lowerFunction` declare-only guard; (6) `lowerFunctionBodyInto` never promotes/lowers
|
|
an extern stub. Hand-authored 1.0b snapshot matched byte-exact — no regen needed. No
|
|
`.ir` snapshot added (the trivial `declare i32 @abs(i32)` doesn't warrant a 1000-line
|
|
full-prelude dump; behavioral `.stdout` already catches ctx/linkage regressions).
|
|
|
|
## 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` FUNCTIONS WORK** (import; bare form, no rename) —
|
|
parse + lower complete, behavior-equivalent to a lib-less `#foreign` fn. Still TODO in
|
|
Phase 1: the `extern LIB "csym"` lib/rename axis (fields exist, unconsumed) and the
|
|
extern-global form. `export` not started (Phase 2). Part B `foreign` footprint to
|
|
purge: 643 lines / ~57 identifiers in `src/` + 28 doc lines. End-state invariant:
|
|
**zero `foreign`** (Phase 9.4 gate). **Done**: 0.0 tokens, 0.1 AST/parser plumbing,
|
|
1.0a fn-path parsing + lib/name fields, 1.0b xfail example, 1.1 fn lowering (green).
|
|
|
|
## Next step
|
|
**Phase 1.2** (green) — two parts, each its own xfail→green or behavior-lock:
|
|
1. **`extern LIB "csym"` rename for fns** — extend `parseOptionalExternExport()` (or
|
|
`parseFnDecl`) to parse the optional `LIB` ident + `"csym"` string after the
|
|
keyword into `FnDecl.extern_lib`/`extern_name`; consume them in `declareFunction`
|
|
(mirror the `#foreign` c_name block at `decl.zig:~2119`: declare under the C name,
|
|
map sx→C in `foreign_name_map`/dedupe). New example renaming a libc symbol (e.g.
|
|
`c_abs :: (n: i32) -> i32 extern "abs";`).
|
|
2. **extern-global `g : T extern [LIB] ["csym"];`** — parse path at `parser.zig:425`
|
|
(the var-decl with type annotation): accept postfix `extern` → set
|
|
`VarDecl.is_extern`/`extern_lib`/`extern_name`; lower like the `#foreign` global
|
|
(`decl.zig:~1115-1137`, `.is_extern = vd.is_foreign`). New example mirroring
|
|
`examples/1205-ffi-foreign-global` with `extern`.
|
|
|
|
Stop at end of Phase 1 (do NOT start Phase 2 `export` or Part B migration). Then the
|
|
A→B gate: a unit test that `#foreign` and `extern` lower to identical IR.
|
|
|
|
## Open decisions
|
|
Part A ratified (bare / postfix / `⇒ callconv(.c)` / lib-separate). Part B (confirm
|
|
before Phase 9): runtime-class rename target — `Runtime*Class*` (recommended);
|
|
historical carve-out — keep `issues/*.md` provenance, gate the live tree only.
|
|
|
|
## Log
|
|
- (init) Plan written; FFI-linkage stream opened.
|
|
- (merge) Folded FOREIGN-MIGRATION in as Part B; deleted the split plan + checkpoint.
|
|
- (0.0) Added `kw_extern`/`kw_export` tokens + keyword-map entries + LSP keyword
|
|
classification + `lex linkage keywords` test. Suite green; no identifier collisions
|
|
in the corpus. `lock` commit.
|
|
- (0.1) Added `ast.ExternExportModifier` + `FnDecl.extern_export` +
|
|
`VarDecl.is_extern`/`extern_name` + `parseOptionalExternExport()` (unconsumed) + 2
|
|
parser unit tests. Suite green (443/633). `lock` commit.
|
|
- (1.0a) Wired fn-path extern parsing (`parseFnDecl` + both lookahead predicates) +
|
|
added `FnDecl.extern_lib`/`extern_name` + `VarDecl.extern_lib` per user feedback
|
|
(decision 4 revised: extern carries an optional lib axis). Unconsumed by lowering.
|
|
Suite green (443/633). `lock` commit.
|
|
- (1.0b) Added `examples/1223-ffi-extern-fn.sx` + hand-authored success snapshots.
|
|
RED (634 ran, 1 failed — sema `body produces no value`). `xfail` commit; 1.1 greens it.
|
|
- (1.1) Wired extern fn lowering (6 edits in `decl.zig`, all declare-only routing
|
|
mirroring `foreign_expr`): `funcWantsImplicitCtx` + `declareFunction` cc +
|
|
`lazyLowerFunction`/`lowerFunction`/`lowerFunctionBodyInto` guards. 1223 green;
|
|
`declare i32 @abs(i32)` (C ABI, no ctx). Suite green (634/443). `green` commit.
|
|
|
|
## Known issues
|
|
None yet.
|