commit 9d827509eeea0d9512f47392b145d3fb1617c626 (tree)
parent d6e2dd2320fc917e847f5a531ddcb130c62b232e
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Thu, 19 Feb 2026 07:26:09 +0000
sema_c: add C Air → Zig Air conversion module
Add stage0/sema_c.zig that converts C Sema output (Air struct) to Zig's
Air type via MultiArrayList, with per-tag data dispatch. Update
stagesCheck to use the conversion, and extend the const x = 42 test to
verify both Air structure and InternPool contents.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
3 files changed, 160 insertions(+), 9 deletions(-)
diff --git a/src/test_exports.zig b/src/test_exports.zig
@@ -1,3 +1,3 @@
pub const InternPool = @import("InternPool.zig");
+pub const Air = @import("Air.zig");
// Later: pub const Sema = @import("Sema.zig");
-// Later: pub const Air = @import("Air.zig");
diff --git a/stage0/sema_c.zig b/stage0/sema_c.zig
@@ -0,0 +1,135 @@
+// sema_c.zig — Convert C Sema output (Air + InternPool) to Zig Air format.
+// Ported mechanically from C structures defined in air.h / sema.h.
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Air = @import("zig_internals").Air;
+
+const sema_test = @import("sema_test.zig");
+pub const c = sema_test.c;
+
+/// Owning wrapper for a Zig Air struct built from C Air data.
+/// Owns the backing MultiArrayList and extra array; call deinit() to free.
+pub const OwnedAir = struct {
+ instructions: std.MultiArrayList(Air.Inst),
+ extra: std.ArrayListUnmanaged(u32),
+
+ pub fn air(self: *const OwnedAir) Air {
+ return .{
+ .instructions = self.instructions.slice(),
+ .extra = self.extra,
+ };
+ }
+
+ pub fn deinit(self: *OwnedAir, gpa: Allocator) void {
+ self.instructions.deinit(gpa);
+ self.extra.deinit(gpa);
+ }
+};
+
+/// Convert a C Air struct (from sema.h) to a Zig Air struct.
+/// The caller owns the returned OwnedAir and must call deinit().
+pub fn zigAir(gpa: Allocator, c_air: c.Air) !OwnedAir {
+ const inst_len: usize = @intCast(c_air.inst_len);
+
+ var instructions = std.MultiArrayList(Air.Inst){};
+ try instructions.ensureTotalCapacity(gpa, inst_len);
+
+ for (0..inst_len) |i| {
+ const tag_u8: u8 = @intCast(c_air.inst_tags.?[i]);
+ const zig_tag: Air.Inst.Tag = @enumFromInt(tag_u8);
+ instructions.appendAssumeCapacity(.{
+ .tag = zig_tag,
+ .data = convertData(zig_tag, c_air.inst_datas.?[i]),
+ });
+ }
+
+ const extra_len: usize = @intCast(c_air.extra_len);
+ var extra = std.ArrayListUnmanaged(u32){};
+ try extra.ensureTotalCapacity(gpa, extra_len);
+ if (extra_len > 0) {
+ extra.appendSliceAssumeCapacity(c_air.extra[0..extra_len]);
+ }
+
+ return .{
+ .instructions = instructions,
+ .extra = extra,
+ };
+}
+
+/// 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 {
+ // Read the raw two u32 words from the C union. Both the C union
+ // (AirInstData) and [2]u32 are 8 bytes with no padding.
+ const raw: [2]u32 = @bitCast(cd);
+
+ return switch (tag) {
+ // --- no_op: void data ---
+ .trap,
+ .breakpoint,
+ .ret_addr,
+ .frame_addr,
+ .unreach,
+ .dbg_empty_stmt,
+ => .{ .no_op = {} },
+
+ // --- dbg_stmt: line + column ---
+ .dbg_stmt => .{ .dbg_stmt = .{
+ .line = raw[0],
+ .column = raw[1],
+ } },
+
+ // --- un_op: single operand ref ---
+ .ret,
+ .ret_safe,
+ .ret_load,
+ => .{ .un_op = @enumFromInt(raw[0]) },
+
+ // --- bin_op: lhs + rhs refs ---
+ .add,
+ .add_safe,
+ .add_optimized,
+ .add_wrap,
+ .add_sat,
+ .sub,
+ .sub_safe,
+ .sub_optimized,
+ .sub_wrap,
+ .sub_sat,
+ .mul,
+ .mul_safe,
+ .mul_optimized,
+ .mul_wrap,
+ .mul_sat,
+ .bit_and,
+ .bit_or,
+ .xor,
+ .bool_and,
+ .bool_or,
+ => .{ .bin_op = .{
+ .lhs = @enumFromInt(raw[0]),
+ .rhs = @enumFromInt(raw[1]),
+ } },
+
+ // --- ty_pl: type ref + payload index ---
+ .block,
+ .@"try",
+ .try_cold,
+ => .{ .ty_pl = .{
+ .ty = @enumFromInt(raw[0]),
+ .payload = raw[1],
+ } },
+
+ // --- br: block_inst + operand ---
+ .br => .{ .br = .{
+ .block_inst = @enumFromInt(raw[0]),
+ .operand = @enumFromInt(raw[1]),
+ } },
+
+ else => std.debug.panic(
+ "unhandled Air tag in C->Zig conversion: {}",
+ .{tag},
+ ),
+ };
+}
diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig
@@ -6,10 +6,12 @@ const AstGen = std.zig.AstGen;
const parser_test = @import("parser_test.zig");
const astgen_test = @import("astgen_test.zig");
const sema_test = @import("sema_test.zig");
+const sema_c = @import("sema_c.zig");
const c = parser_test.c;
const sc = sema_test.c;
const zig_internals = @import("zig_internals");
const ZigIP = zig_internals.InternPool;
+const ZigAir = zig_internals.Air;
const Stage = enum { parser, sema };
@@ -69,17 +71,22 @@ fn stagesCheck(gpa: Allocator, source: [:0]const u8, check: Stage) !void {
defer sc.ipDeinit(&ip);
var sema = sc.semaInit(&ip, @bitCast(c_zir));
defer sc.semaDeinit(&sema);
- var air = sc.semaAnalyze(&sema);
- defer sc.airDeinit(&air);
+ var c_air = sc.semaAnalyze(&sema);
+ defer sc.airDeinit(&c_air);
if (sema.has_compile_errors) {
std.debug.print("Sema returned compile errors\n", .{});
return error.TestUnexpectedResult;
}
- // Verify Air arrays are properly transferred.
- try std.testing.expect(air.inst_tags != null);
- try std.testing.expect(air.inst_datas != null);
+ // Convert C Air to Zig Air for comparison.
+ var owned_air = try sema_c.zigAir(gpa, c_air);
+ defer owned_air.deinit(gpa);
+ const zig_air = owned_air.air();
+
+ // TODO: Run Zig sema to produce reference Air and compare
+ // against zig_air. For now, verify the conversion is valid.
+ _ = zig_air;
}
}
@@ -1318,7 +1325,7 @@ fn expectKeysEqual(c_key: sc.InternPoolKey, zig_key: ZigIP.Key, index: u32) !voi
}
}
-test "sema: const x = 42 intern pool comparison" {
+test "sema: const x = 42 air + intern pool comparison" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "const x = 42;";
@@ -1332,8 +1339,17 @@ test "sema: const x = 42 intern pool comparison" {
defer sc.ipDeinit(&c_ip);
var sema = sc.semaInit(&c_ip, @bitCast(c_zir));
defer sc.semaDeinit(&sema);
- var air = sc.semaAnalyze(&sema);
- defer sc.airDeinit(&air);
+ var c_air = sc.semaAnalyze(&sema);
+ defer sc.airDeinit(&c_air);
+
+ // Convert C Air to Zig Air.
+ var owned_air = try sema_c.zigAir(gpa, c_air);
+ defer owned_air.deinit(gpa);
+ const zig_air = owned_air.air();
+
+ // For `const x = 42;`, sema produces no AIR instructions
+ // (everything is resolved at comptime).
+ try std.testing.expectEqual(@as(usize, 0), zig_air.instructions.len);
// C IP should have grown beyond 124 pre-interned entries
try std.testing.expect(c_ip.items_len > 124);