feat(ffi-linkage): lower extern data globals (Phase 1.2d) — Phase 1 complete

Parser: a 'kw_extern' branch in the var-decl-with-type-annotation path
(beside #foreign) parses 'name : type extern [LIB] ["csym"];' into
VarDecl.is_extern/extern_lib/extern_name; the trailing diagnostic now
lists 'extern'. Lowering: registerTopLevelGlobal 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
initializer) for extern globals too.

examples/1225 green: '__stdinp : *void extern;' lowers to
'@__stdinp = external global ptr'; @__stdinp reads non-null. Suite
green (636 corpus / 443 unit).

Phase 1 done: extern functions (bare + rename) and data globals (bare +
rename) all work, behavior-equivalent to the matching #foreign form.
export (Phase 2), aggregates (Phase 3), docs + A->B gate (Phase 4)
remain. green commit.
This commit is contained in:
agra
2026-06-14 13:39:05 +03:00
parent 235f74a8c9
commit 6932426c41
3 changed files with 78 additions and 40 deletions

View File

@@ -1112,10 +1112,11 @@ pub fn registerTopLevelGlobal(self: *Lowering, vd: *const ast.VarDecl) void {
d.addFmt(.err, null, "top-level var '{s}' has no type annotation and no initializer to infer from", .{vd.name});
break :blk .void;
};
// Foreign globals reference a symbol defined in libSystem etc.
// (`_NSConcreteStackBlock : *void #foreign;`). The C symbol
// name is the optional override or the sx name itself.
const sym_name = vd.foreign_name orelse vd.name;
// Foreign / extern globals reference a symbol defined in libSystem etc.
// (`_NSConcreteStackBlock : *void #foreign;` or `… : *void extern;`). The C
// symbol name is the optional override (`extern_name`/`foreign_name`) or the
// sx name itself.
const sym_name = vd.extern_name orelse vd.foreign_name orelse vd.name;
const name_id = self.module.types.internString(sym_name);
const init_val = self.globalInitValue(vd, var_ty);
const gid = self.module.addGlobal(.{
@@ -1123,7 +1124,7 @@ pub fn registerTopLevelGlobal(self: *Lowering, vd: *const ast.VarDecl) void {
.ty = var_ty,
.init_val = init_val,
.is_const = false,
.is_extern = vd.is_foreign,
.is_extern = vd.is_foreign or vd.is_extern,
});
self.putGlobal(self.current_source_file, vd.name, .{ .id = gid, .ty = var_ty });
}
@@ -1137,7 +1138,7 @@ pub fn registerTopLevelGlobal(self: *Lowering, vd: *const ast.VarDecl) void {
/// is rejected with a diagnostic rather than silently zero-initialized — a
/// global has no run site for a dynamic initializer.
pub fn globalInitValue(self: *Lowering, vd: *const ast.VarDecl, var_ty: TypeId) ?inst_mod.ConstantValue {
if (vd.is_foreign) return null;
if (vd.is_foreign or vd.is_extern) return null;
const v = vd.value orelse return null;
return switch (v.data) {
.undef_literal => .zeroinit,

View File

@@ -449,7 +449,35 @@ pub const Parser = struct {
} });
}
return self.fail("expected ':', '=', ';' or '#foreign' after type annotation");
if (self.current.tag == .kw_extern) {
// name : type extern [LIB] ["csym"]; (extern data global — the
// extern-named counterpart of `#foreign`; resolved at link time)
self.advance();
var ext_lib: ?[]const u8 = null;
if (self.current.tag == .identifier) {
ext_lib = self.tokenSlice(self.current);
self.advance();
}
var ext_name: ?[]const u8 = null;
if (self.current.tag == .string_literal) {
const raw = self.tokenSlice(self.current);
ext_name = raw[1 .. raw.len - 1];
self.advance();
}
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .var_decl = .{
.name = name,
.name_span = name_span,
.type_annotation = type_node,
.value = null,
.is_extern = true,
.extern_lib = ext_lib,
.extern_name = ext_name,
.is_raw = name_is_raw,
} });
}
return self.fail("expected ':', '=', ';', '#foreign', or 'extern' after type annotation");
}
fn parseTypeExpr(self: *Parser) anyerror!*Node {