zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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:
Mstage0/sema.c | 56++++++++++++++++++++++++++++++++------------------------
Mstage0/sema.h | 17++++++++++++++++-
Mstage0/sema_c.zig | 76+++++++++++++++++++++++++++++-----------------------------------------------
Mstage0/sema_test.zig | 14++++++++++----
Mstage0/stages_test.zig | 8++------
Mstage0/zig0.c | 4++--
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;