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>
This commit is contained in:
2026-02-19 07:26:09 +00:00
parent db33d34d6d
commit 613ab41353
3 changed files with 160 additions and 9 deletions

View File

@@ -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");

135
stage0/sema_c.zig Normal file
View File

@@ -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},
),
};
}

View File

@@ -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);