feat(ffi-linkage): lower extern fns as C imports (Phase 1.1)
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).
This commit is contained in:
@@ -387,6 +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;
|
||||
return switch (fd.body.data) {
|
||||
.foreign_expr, .builtin_expr, .compiler_expr => false,
|
||||
else => !isExportedEntryName(fd.name),
|
||||
@@ -2078,6 +2081,12 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
// calling convention's `...` tail. Drop the variadic param from the
|
||||
// IR signature (it has no C-level slot) and set is_variadic.
|
||||
const is_foreign = fd.body.data == .foreign_expr;
|
||||
// Bare `extern` import: an external C symbol declared via the new linkage
|
||||
// surface (empty-block placeholder body, no `foreign_expr`). It shares
|
||||
// `#foreign`'s C-ABI promotion + declareExtern routing below; the optional
|
||||
// `extern LIB "csym"` lib/rename axis (extern_lib/extern_name) is consumed
|
||||
// in Phase 1.2. (`export` defines take the beginFunction path, not here.)
|
||||
const is_extern_decl = fd.extern_export == .extern_;
|
||||
var is_variadic = false;
|
||||
var effective_params = fd.params;
|
||||
if (is_foreign and fd.params.len > 0 and fd.params[fd.params.len - 1].is_variadic) {
|
||||
@@ -2107,7 +2116,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) .c else .default;
|
||||
const cc: Function.CallingConvention = if (fd.call_conv == .c or is_foreign or is_extern_decl) .c else .default;
|
||||
|
||||
// For #foreign with C name override, declare under C name and map sx name → C name
|
||||
if (is_foreign) {
|
||||
@@ -2284,7 +2293,7 @@ pub fn lazyLowerFunction(self: *Lowering, name: []const u8) void {
|
||||
// a fresh ct_module via `evalComptimeString`) emits `.call` against a
|
||||
// FuncId that doesn't exist locally; the interp can't find the
|
||||
// foreign target and silently no-ops instead of dispatching to libc.
|
||||
if (fd.body.data == .foreign_expr) {
|
||||
if (fd.body.data == .foreign_expr or fd.extern_export == .extern_) {
|
||||
if (self.resolveFuncByName(name) == null) {
|
||||
self.declareFunction(fd, name);
|
||||
self.lowered_functions.put(name, {}) catch {};
|
||||
@@ -2372,6 +2381,11 @@ pub fn lowerFunctionBodyInto(self: *Lowering, fd: *const ast.FnDecl, fid: FuncId
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
self.setCurrentSourceFile(func.source_file);
|
||||
|
||||
// `extern` imports are pure declarations — never promote the stub to a real
|
||||
// function or lower the (empty placeholder) body. Mirrors the declare-only
|
||||
// handling in lowerFunction / lazyLowerFunction.
|
||||
if (fd.extern_export == .extern_) return;
|
||||
|
||||
const ret_ty = self.resolveReturnType(fd);
|
||||
|
||||
if (!func.is_extern) {
|
||||
@@ -2477,8 +2491,9 @@ pub fn lowerFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, i
|
||||
}) catch unreachable;
|
||||
}
|
||||
|
||||
// Check if the function body is a builtin or foreign declaration (no body needed)
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr or fd.body.data == .compiler_expr) {
|
||||
// Check if the function body is a builtin or foreign declaration (no body
|
||||
// needed). `extern` imports are declare-only too (empty placeholder body).
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr or fd.body.data == .compiler_expr or fd.extern_export == .extern_) {
|
||||
// Already declared by scanDecls/declareFunction (which handles #foreign renames)
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user