commit 01830c7af641b5646ad0ffcbac3c23ae3c6c41ee (tree)
parent 9d5e62aba556d6c656ca7459e1d6687a9013e42e
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Thu, 19 Feb 2026 12:15:04 +0000
sema: return per-function Air list instead of flat module-wide Air
Change semaAnalyze to return SemaFuncAirList (a list of per-function Air
structs) instead of a single flat Air. This mirrors the Zig compiler's
per-function Air architecture. Currently returns an empty list since
zirFunc is not yet ported; module-level Air arrays are freed directly.
Update sema_c.zig, sema_test.zig, stages_test.zig, and zig0.c to use
the new types. Remove expectEqualAir (replaced by textual comparison
in a subsequent commit).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
6 files changed, 91 insertions(+), 84 deletions(-)
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -259,28 +259,27 @@ static bool declIdHasName(uint32_t id) {
}
static bool declIdHasLibName(uint32_t id) {
- return id == 11 || id == 13
- || id == 22 || id == 23 || id == 24 || id == 25;
+ return id == 11 || id == 13 || id == 22 || id == 23 || id == 24
+ || id == 25;
}
static bool declIdHasTypeBody(uint32_t id) {
// false for untyped constructs (0-3) and simple reprs (4,7,16,19)
- return !(id == 0 || id == 1 || id == 2 || id == 3
- || id == 4 || id == 7 || id == 16 || id == 19);
+ return !(id == 0 || id == 1 || id == 2 || id == 3 || id == 4 || id == 7
+ || id == 16 || id == 19);
}
static bool declIdHasValueBody(uint32_t id) {
// false for extern IDs (10-13, 22-25)
- return !(id == 10 || id == 11 || id == 12 || id == 13
- || id == 22 || id == 23 || id == 24 || id == 25);
+ return !(id == 10 || id == 11 || id == 12 || id == 13 || id == 22
+ || id == 23 || id == 24 || id == 25);
}
static bool declIdHasSpecialBodies(uint32_t id) {
// false for untyped (0-3), simple const (4,5,7,8),
// extern_const_simple (10,12), simple var (16,19)
- return !(id == 0 || id == 1 || id == 2 || id == 3
- || id == 4 || id == 5 || id == 7 || id == 8
- || id == 10 || id == 12 || id == 16 || id == 19);
+ return !(id == 0 || id == 1 || id == 2 || id == 3 || id == 4 || id == 5
+ || id == 7 || id == 8 || id == 10 || id == 12 || id == 16 || id == 19);
}
// Forward declaration for recursive call from zirStructDecl.
@@ -513,7 +512,7 @@ static bool analyzeBodyInner(
// ZIR instruction 0 is always ZIR_INST_EXTENDED with opcode
// ZIR_EXT_STRUCT_DECL, representing the root module struct.
-Air semaAnalyze(Sema* sema) {
+SemaFuncAirList semaAnalyze(Sema* sema) {
// Exercise utility functions to satisfy cppcheck unusedFunction.
InternPool* ip = sema->ip;
@@ -550,19 +549,13 @@ Air semaAnalyze(Sema* sema) {
semaBlockDeinit(&root_block);
- // Build the output Air from sema's arrays.
- // Transfer ownership of the instruction arrays to Air.
- Air air;
- memset(&air, 0, sizeof(air));
- air.inst_tags = sema->air_inst_tags;
- air.inst_datas = sema->air_inst_datas;
- air.inst_len = sema->air_inst_len;
- air.inst_cap = sema->air_inst_cap;
- air.extra = sema->air_extra;
- air.extra_len = sema->air_extra_len;
- air.extra_cap = sema->air_extra_cap;
-
- // Null out sema's arrays so semaDeinit won't double-free.
+ // Free the module-level Air arrays from sema (they're empty for
+ // comptime-only modules but allocated).
+ free(sema->air_inst_tags);
+ free(sema->air_inst_datas);
+ free(sema->air_extra);
+
+ // Null out sema's pointers to prevent double-free in semaDeinit.
sema->air_inst_tags = NULL;
sema->air_inst_datas = NULL;
sema->air_inst_len = 0;
@@ -571,5 +564,20 @@ Air semaAnalyze(Sema* sema) {
sema->air_extra_len = 0;
sema->air_extra_cap = 0;
- return air;
+ // Return an empty SemaFuncAirList (no functions analyzed until
+ // zirFunc is ported).
+ SemaFuncAirList result;
+ memset(&result, 0, sizeof(result));
+ return result;
+}
+
+void semaFuncAirListDeinit(SemaFuncAirList* list) {
+ for (uint32_t i = 0; i < list->len; i++) {
+ free(list->items[i].name);
+ airDeinit(&list->items[i].air);
+ }
+ free(list->items);
+ list->items = NULL;
+ list->len = 0;
+ list->cap = 0;
}
diff --git a/stage0/sema.h b/stage0/sema.h
@@ -103,6 +103,20 @@ typedef struct {
uint32_t runtime_index;
} ComptimeAlloc;
+// --- SemaFuncAir ---
+// Per-function Air result, produced during sema analysis.
+
+typedef struct {
+ char* name; // function FQN (owned, null-terminated)
+ Air air; // per-function Air (owns its arrays)
+} SemaFuncAir;
+
+typedef struct {
+ SemaFuncAir* items;
+ uint32_t len;
+ uint32_t cap;
+} SemaFuncAirList;
+
// --- Sema ---
// State used for compiling a ZIR into AIR.
// Transforms untyped ZIR instructions into semantically-analyzed AIR
@@ -138,6 +152,7 @@ typedef struct Sema {
Sema semaInit(InternPool* ip, Zir code);
void semaDeinit(Sema* sema);
-Air semaAnalyze(Sema* sema);
+SemaFuncAirList semaAnalyze(Sema* sema);
+void semaFuncAirListDeinit(SemaFuncAirList* list);
#endif
diff --git a/stage0/sema_c.zig b/stage0/sema_c.zig
@@ -8,28 +8,32 @@ const Air = @import("zig_internals").Air;
const sema_test = @import("sema_test.zig");
pub const c = sema_test.c;
+pub const FuncAir = struct {
+ name: []const u8,
+ owned_air: OwnedAir,
+};
+
/// Result of running C sema on a ZIR and converting to Zig Air.
-/// Owns the C InternPool, C Sema, C Air, and the converted Zig Air.
+/// Owns the C InternPool, C Sema, per-function Airs, and the converted Zig Airs.
/// Call deinit() to free everything.
pub const SemaResult = struct {
- owned_air: OwnedAir,
- c_air: c.Air,
+ func_airs: []FuncAir,
+ c_func_air_list: c.SemaFuncAirList,
c_ip: c.InternPool,
c_sema: c.Sema,
- pub fn air(self: *const SemaResult) Air {
- return self.owned_air.air();
- }
-
pub fn deinit(self: *SemaResult, gpa: Allocator) void {
- self.owned_air.deinit(gpa);
- c.airDeinit(&self.c_air);
+ for (self.func_airs) |*fa| {
+ fa.owned_air.deinit(gpa);
+ }
+ gpa.free(self.func_airs);
+ c.semaFuncAirListDeinit(&self.c_func_air_list);
c.semaDeinit(&self.c_sema);
c.ipDeinit(&self.c_ip);
}
};
-/// Run C sema on a C ZIR, then convert the resulting C Air to Zig Air.
+/// Run C sema on a C ZIR, then convert the resulting per-function C Airs to Zig Air.
/// The caller retains ownership of c_zir (and any backing c_ast).
pub fn cSema(gpa: Allocator, c_zir: c.Zir) !SemaResult {
var c_ip = c.ipInit();
@@ -38,18 +42,28 @@ pub fn cSema(gpa: Allocator, c_zir: c.Zir) !SemaResult {
var c_sema = c.semaInit(&c_ip, c_zir);
errdefer c.semaDeinit(&c_sema);
- var c_air = c.semaAnalyze(&c_sema);
- errdefer c.airDeinit(&c_air);
+ var c_func_air_list = c.semaAnalyze(&c_sema);
+ errdefer c.semaFuncAirListDeinit(&c_func_air_list);
if (c_sema.has_compile_errors) {
return error.SemaCompileError;
}
- const owned_air = try zigAir(gpa, c_air);
+ // Convert each C per-function Air to Zig Air
+ const func_airs = try gpa.alloc(FuncAir, c_func_air_list.len);
+ errdefer gpa.free(func_airs);
+
+ for (0..c_func_air_list.len) |i| {
+ const c_item = c_func_air_list.items[i];
+ func_airs[i] = .{
+ .name = std.mem.span(c_item.name),
+ .owned_air = try zigAir(gpa, c_item.air),
+ };
+ }
return .{
- .owned_air = owned_air,
- .c_air = c_air,
+ .func_airs = func_airs,
+ .c_func_air_list = c_func_air_list,
.c_ip = c_ip,
.c_sema = c_sema,
};
@@ -104,38 +118,6 @@ pub fn zigAir(gpa: Allocator, c_air: c.Air) !OwnedAir {
};
}
-/// Verify that a converted Zig Air matches the original C Air.
-/// Checks instruction count, tag values, and extra data.
-/// Data comparison is done via re-conversion from C (since Zig's
-/// Air.Inst.Data is a bare union with no guaranteed in-memory layout).
-pub fn expectEqualAir(zig_air: Air, c_air: c.Air) !void {
- const inst_len: usize = @intCast(c_air.inst_len);
- try std.testing.expectEqual(inst_len, zig_air.instructions.len);
-
- const zig_tags = zig_air.instructions.items(.tag);
-
- for (0..inst_len) |i| {
- const c_tag: u8 = @intCast(c_air.inst_tags.?[i]);
- const zig_tag: u8 = @intFromEnum(zig_tags[i]);
- if (c_tag != zig_tag) {
- std.debug.print("Air tag mismatch at inst {d}: C={d} Zig={d}\n", .{ i, c_tag, zig_tag });
- return error.TestExpectedEqual;
- }
- }
-
- const extra_len: usize = @intCast(c_air.extra_len);
- try std.testing.expectEqual(extra_len, zig_air.extra.items.len);
-
- for (0..extra_len) |i| {
- if (c_air.extra[i] != zig_air.extra.items[i]) {
- std.debug.print("Air extra mismatch at index {d}: C={d} Zig={d}\n", .{
- i, c_air.extra[i], zig_air.extra.items[i],
- });
- return error.TestExpectedEqual;
- }
- }
-}
-
/// Convert a C AirInstData union to a Zig Air.Inst.Data union,
/// dispatching on the tag to construct the correct Zig variant.
fn convertData(tag: Air.Inst.Tag, cd: c.AirInstData) Air.Inst.Data {
diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig
@@ -183,25 +183,31 @@ test "sema: empty source smoke test" {
var result = try semaCheck(gpa, "");
defer result.deinit(gpa);
- // semaAnalyze transfers ownership of AIR arrays to Air.
- // After transfer, sema's arrays should be nulled out.
+ // semaAnalyze frees AIR arrays and nulls out sema's pointers.
try std.testing.expect(result.c_sema.air_inst_tags == null);
try std.testing.expect(result.c_sema.air_inst_datas == null);
try std.testing.expect(result.c_sema.air_extra == null);
+
+ // No functions analyzed yet, so func_airs should be empty.
+ try std.testing.expectEqual(@as(usize, 0), result.func_airs.len);
}
test "sema: const x = 0 smoke test" {
const gpa = std.testing.allocator;
var result = try semaCheck(gpa, "const x = 0;");
defer result.deinit(gpa);
- _ = result.air();
+
+ // No functions, so func_airs should be empty.
+ try std.testing.expectEqual(@as(usize, 0), result.func_airs.len);
}
test "sema: function decl smoke test" {
const gpa = std.testing.allocator;
var result = try semaCheck(gpa, "fn foo() void {}");
defer result.deinit(gpa);
- _ = result.air();
+
+ // zirFunc not yet ported, so func_airs should be empty.
+ try std.testing.expectEqual(@as(usize, 0), result.func_airs.len);
}
// ---------------------------------------------------------------------------
diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig
@@ -71,9 +71,6 @@ fn stagesCheck(gpa: Allocator, source: [:0]const u8, src_path: []const u8, check
var result = try sema_c.cSema(gpa, @bitCast(c_zir));
defer result.deinit(gpa);
- // Verify C→Zig Air conversion is faithful (tags, data, extra).
- try sema_c.expectEqualAir(result.air(), result.c_air);
-
// Run Zig sema on the same source and verify it succeeds.
var zig_result = try sema.zigSema(gpa, src_path);
defer zig_result.deinit();
@@ -1327,11 +1324,10 @@ test "sema: const x = 42 air + intern pool comparison" {
var result = try sema_c.cSema(gpa, @bitCast(c_zir));
defer result.deinit(gpa);
- const zig_air = result.air();
- // For `const x = 42;`, sema produces no AIR instructions
+ // For `const x = 42;`, sema produces no per-function Airs
// (everything is resolved at comptime).
- try std.testing.expectEqual(@as(usize, 0), zig_air.instructions.len);
+ try std.testing.expectEqual(@as(usize, 0), result.func_airs.len);
// C IP should have grown beyond 124 pre-interned entries
try std.testing.expect(result.c_ip.items_len > 124);
diff --git a/stage0/zig0.c b/stage0/zig0.c
@@ -42,9 +42,9 @@ static int zig0Run(const char* program, char** msg) {
InternPool ip = ipInit();
Sema sema = semaInit(&ip, zir);
- Air air = semaAnalyze(&sema);
+ SemaFuncAirList func_airs = semaAnalyze(&sema);
semaDeinit(&sema);
- airDeinit(&air);
+ semaFuncAirListDeinit(&func_airs);
ipDeinit(&ip);
zirDeinit(&zir);
return 0;