From a47ef20ad304dac42a2b8ad389f89d41d7579734 Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 14 Jun 2026 14:45:16 +0300 Subject: [PATCH] =?UTF-8?q?feat(ffi-linkage):=20lower=20`export`=20fns=20(?= =?UTF-8?q?Phase=202.1)=20=E2=80=94=20example=201226=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `export` (define + expose) now lowers to a defined C-ABI symbol with external linkage and no implicit sx context — the four export-gap conditions in src/ir/lower/decl.zig: - (i) linkage: force `.external` for `extern_export == .export_` on both define paths (lowerFunctionBodyInto, lowerFunction), beside the OS-called entry points. - (ii) C ABI: promote call_conv to `.c` on the define paths and in the declareFunction extern-stub cc. - (iv) no ctx: funcWantsImplicitCtx returns false for any non-`.none` modifier (extern AND export), so no `__sx_ctx` slot is prepended. - force-lower: an `export` fn is a lowering root (like `main`) in lowerMainAndComptime — its purpose is external consumption, so it must emit a body even when no sx code calls it; otherwise lazy lowering leaves it a bodiless `declare`. example/1226 now builds + runs via the AOT corpus mode: the companion C calls `sx_square` by name and prints 37 / 82. Suite green (637 corpus / 443 unit). The optional `export "csym"` rename (gap iii) is Phase 2.2. --- src/ir/lower/decl.zig | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/ir/lower/decl.zig b/src/ir/lower/decl.zig index 6343964..3e95732 100644 --- a/src/ir/lower/decl.zig +++ b/src/ir/lower/decl.zig @@ -387,9 +387,9 @@ pub fn detectContextDecl(decls: []const *const Node) bool { pub fn funcWantsImplicitCtx(self: *const Lowering, fd: *const ast.FnDecl) bool { if (!self.implicit_ctx_enabled) return false; if (fd.call_conv == .c) return false; - // `extern` imports are external C symbols — C ABI, no sx context. - // (`export` defines get the same treatment in Phase 2, gap iv.) - if (fd.extern_export == .extern_) return false; + // `extern` imports and `export` defines are external C symbols — + // C ABI, no sx context (Phase 2, gap iv). + if (fd.extern_export != .none) return false; return switch (fd.body.data) { .foreign_expr, .builtin_expr, .compiler_expr => false, else => !isExportedEntryName(fd.name), @@ -1319,7 +1319,11 @@ pub fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void { switch (decl.data) { .const_decl => |cd| { if (cd.value.data == .fn_decl) { - if (isExportedEntryName(cd.name)) { + // `export` defines are roots: their purpose is external + // consumption (often never called from sx), so force-lower + // them like OS-called entry points — else lazy lowering + // leaves them as bodiless `declare` stubs (Phase 2). + if (isExportedEntryName(cd.name) or cd.value.data.fn_decl.extern_export == .export_) { self.lazyLowerFunction(cd.name); } } else if (cd.value.data == .comptime_expr) { @@ -1327,7 +1331,7 @@ pub fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void { } }, .fn_decl => |fd| { - if (isExportedEntryName(fd.name)) { + if (isExportedEntryName(fd.name) or fd.extern_export == .export_) { self.lazyLowerFunction(fd.name); } }, @@ -2117,7 +2121,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) // typed by name as `(args) -> ret` of a `#foreign` decl can be // assigned to / passed as a `callconv(.c)` fn-pointer without a // call-convention mismatch. - const cc: Function.CallingConvention = if (fd.call_conv == .c or is_foreign or is_extern_decl) .c else .default; + const cc: Function.CallingConvention = if (fd.call_conv == .c or is_foreign or is_extern_decl or fd.extern_export == .export_) .c else .default; // Symbol-name override: `#foreign … "csym"` (foreign_expr.c_name) or the new // `extern … "csym"` (fd.extern_name). Declare under the C name and map the sx @@ -2399,8 +2403,9 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId return; } func.is_extern = false; // promote from extern stub to real function - func.linkage = if (isExportedEntryName(name)) .external else .internal; - if (fd.call_conv == .c) func.call_conv = .c; + // `export` defines force external linkage + C ABI (Phase 2, gaps i+ii). + func.linkage = if (isExportedEntryName(name) or fd.extern_export == .export_) .external else .internal; + if (fd.call_conv == .c or fd.extern_export == .export_) func.call_conv = .c; // Set inst_counter to param count (params occupy refs 0..N-1). IR params // = AST params + 1 if the function carries `__sx_ctx` at slot 0. const ctx_slots: usize = if (func.has_implicit_ctx) 1 else 0; @@ -2535,12 +2540,14 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i // matches C `static`). isExportedEntryName lists the names the OS // loader calls — `main`, Android NativeActivity hooks — which must // stay externally visible. - if (isExportedEntryName(name)) { + // `export` defines force external linkage (Phase 2, gap i) alongside + // the OS-called entry points. + if (isExportedEntryName(name) or fd.extern_export == .export_) { self.builder.currentFunc().linkage = .external; } - // Set calling convention - if (fd.call_conv == .c) { + // Set calling convention. `export` defines promote to C ABI (gap ii). + if (fd.call_conv == .c or fd.extern_export == .export_) { self.builder.currentFunc().call_conv = .c; }