commit 3c118e1e0d09eb68d43001a75ffe8f11c60d8ee8 (tree)
parent 040a65cf435f65f97b298211838c0ac2628cfa54
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Sat, 21 Feb 2026 15:20:06 +0000
verbose_air: add human-readable AIR printer and --verbose-air CLI flag
Add verbose_air.c/h implementing a human-readable AIR printer for
debugging the C sema, ported from src/Air/print.zig. Types print as
human-readable names (u32, *const u8, fn (...) noreturn) instead of
raw IP indices. Add --verbose-air flag to zig0 CLI and a `zig build
zig0` target for building the standalone executable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
8 files changed, 1196 insertions(+), 10 deletions(-)
diff --git a/build.zig b/build.zig
@@ -10,8 +10,34 @@ const assert = std.debug.assert;
const DevEnv = @import("src/dev.zig").Env;
const ValueInterpretMode = enum { direct, by_name };
-const zig0_headers = &[_][]const u8{ "common.h", "ast.h", "parser.h", "zir.h", "astgen.h", "intern_pool.h", "air.h", "type.h", "value.h", "sema.h", "dump.h" };
-const zig0_c_lib_files = &[_][]const u8{ "tokenizer.c", "ast.c", "zig0.c", "parser.c", "zir.c", "astgen.c", "intern_pool.c", "air.c", "type.c", "value.c", "sema.c" };
+const zig0_headers = &[_][]const u8{
+ "air.h",
+ "ast.h",
+ "astgen.h",
+ "common.h",
+ "dump.h",
+ "intern_pool.h",
+ "parser.h",
+ "sema.h",
+ "type.h",
+ "value.h",
+ "verbose_air.h",
+ "zir.h",
+};
+const zig0_c_lib_files = &[_][]const u8{
+ "air.c",
+ "ast.c",
+ "astgen.c",
+ "intern_pool.c",
+ "parser.c",
+ "sema.c",
+ "tokenizer.c",
+ "type.c",
+ "value.c",
+ "verbose_air.c",
+ "zig0.c",
+ "zir.c",
+};
const zig0_all_c_files = zig0_c_lib_files ++ &[_][]const u8{"main.c"};
const zig0_cflags = &[_][]const u8{
"-std=c11",
@@ -712,6 +738,25 @@ pub fn build(b: *std.Build) !void {
const test_zig0_step = b.step("test-zig0", "Run zig0 C implementation tests");
addZig0TestStep(b, test_zig0_step, zig0_target, optimize, zig0_cc, zig0_no_exec, zig0_valgrind, zig0_test_timeout, zig0_exe_options);
+ // zig0 standalone executable
+ const zig0_step = b.step("zig0", "Build zig0 standalone executable");
+ const zig0_mod = b.createModule(.{
+ .target = zig0_target,
+ .optimize = optimize,
+ });
+ zig0_mod.addCSourceFiles(.{
+ .root = b.path("stage0"),
+ .files = zig0_all_c_files,
+ .flags = zig0_cflags,
+ });
+ zig0_mod.linkSystemLibrary("c", .{});
+ const zig0_exe = b.addExecutable(.{
+ .name = "zig0",
+ .root_module = zig0_mod,
+ });
+ const zig0_install = b.addInstallArtifact(zig0_exe, .{});
+ zig0_step.dependOn(&zig0_install.step);
+
const fmt_zig0 = b.step("fmt-zig0", "Format zig0 C code");
const clang_format = b.addSystemCommand(&.{ "clang-format", "-i" });
for (zig0_all_c_files ++ zig0_headers) |f| clang_format.addFileArg(b.path(b.fmt("stage0/{s}", .{f})));
diff --git a/src/verbose_air.zig b/src/verbose_air.zig
@@ -24,6 +24,7 @@ const CAir = extern struct {
/// Matches C `SemaFuncAir` struct layout (sema.h).
const CSemaFuncAir = extern struct {
name: ?[*:0]u8,
+ func_ip: u32, // InternPoolIndex
air: CAir,
};
@@ -98,6 +99,7 @@ const AirCollector = struct {
try self.funcs.append(gpa, .{
.name = name_copy.ptr,
+ .func_ip = std.math.maxInt(u32), // IP_INDEX_NONE
.air = .{
.inst_len = inst_len,
.inst_cap = inst_len,
diff --git a/stage0/main.c b/stage0/main.c
@@ -3,22 +3,41 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
-int zig0Run(char* program, char** msg);
-int zig0RunFile(char* fname, char** msg);
+int zig0RunFile(const char* fname, bool verbose_air, char** msg);
static void usage(const char* argv0) {
- fprintf(stderr, "Usage: %s program.zig\n", argv0);
+ fprintf(stderr, "Usage: %s [--verbose-air] program.zig\n", argv0);
}
int main(int argc, char** argv) {
- if (argc != 2) {
+ bool verbose_air = false;
+ const char* fname = NULL;
+
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--verbose-air") == 0) {
+ verbose_air = true;
+ } else if (argv[i][0] == '-') {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ usage(argv[0]);
+ return 1;
+ } else if (fname == NULL) {
+ fname = argv[i];
+ } else {
+ fprintf(stderr, "Unexpected argument: %s\n", argv[i]);
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (fname == NULL) {
usage(argv[0]);
return 1;
}
char* msg;
- switch (zig0RunFile(argv[1], &msg)) {
+ switch (zig0RunFile(fname, verbose_air, &msg)) {
case 0:
return 0;
break;
diff --git a/stage0/sema.c b/stage0/sema.c
@@ -3241,6 +3241,7 @@ static void zirFunc(Sema* sema, SemaBlock* block, uint32_t inst) {
}
SemaFuncAir* entry = &list->items[list->len++];
entry->name = func_name;
+ entry->func_ip = IP_INDEX_NONE;
entry->air.inst_tags = sema->air_inst_tags;
entry->air.inst_datas = sema->air_inst_datas;
entry->air.inst_len = sema->air_inst_len;
diff --git a/stage0/sema.h b/stage0/sema.h
@@ -110,6 +110,7 @@ typedef struct {
typedef struct {
char* name; // function FQN (owned, null-terminated)
+ InternPoolIndex func_ip; // IP index of function value (for name resolution)
Air air; // per-function Air (owns its arrays)
} SemaFuncAir;
diff --git a/stage0/verbose_air.c b/stage0/verbose_air.c
@@ -0,0 +1,1102 @@
+// verbose_air.c — Human-readable AIR printer for debugging.
+// Ported from src/Air/print.zig.
+
+#include "verbose_air.h"
+#include "air.h"
+#include "intern_pool.h"
+#include "sema.h"
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+// --- Tag name table (generated from X-macro) ---
+
+#define AIR_TAG_STR(e) #e,
+static const char* const air_tag_strs[] = {
+ AIR_INST_FOREACH_TAG(AIR_TAG_STR)};
+
+// Print tag name: strip "AIR_INST_" prefix (9 chars) and lowercase.
+static void writeTagName(FILE* out, uint8_t tag) {
+ const char* raw = air_tag_strs[tag] + 9; // skip "AIR_INST_"
+ for (const char* p = raw; *p; p++) {
+ fputc(tolower((unsigned char)*p), out);
+ }
+}
+
+// --- Static IP name table (124 entries, matching InternPool.Index enum) ---
+
+static const char* const ip_static_names[IP_INDEX_PREINTERN_COUNT] = {
+ // Types (0-17): integer types
+ "u0_type", // 0
+ "i0_type", // 1
+ "u1_type", // 2
+ "u8_type", // 3
+ "i8_type", // 4
+ "u16_type", // 5
+ "i16_type", // 6
+ "u29_type", // 7
+ "u32_type", // 8
+ "i32_type", // 9
+ "u64_type", // 10
+ "i64_type", // 11
+ "u80_type", // 12
+ "u128_type", // 13
+ "i128_type", // 14
+ "u256_type", // 15
+ "usize_type", // 16
+ "isize_type", // 17
+ // C types (18-27)
+ "c_char_type", // 18
+ "c_short_type", // 19
+ "c_ushort_type", // 20
+ "c_int_type", // 21
+ "c_uint_type", // 22
+ "c_long_type", // 23
+ "c_ulong_type", // 24
+ "c_longlong_type", // 25
+ "c_ulonglong_type", // 26
+ "c_longdouble_type", // 27
+ // Float types (28-32)
+ "f16_type", // 28
+ "f32_type", // 29
+ "f64_type", // 30
+ "f80_type", // 31
+ "f128_type", // 32
+ // Special types (33-44)
+ "anyopaque_type", // 33
+ "bool_type", // 34
+ "void_type", // 35
+ "type_type", // 36
+ "anyerror_type", // 37
+ "comptime_int_type", // 38
+ "comptime_float_type", // 39
+ "noreturn_type", // 40
+ "anyframe_type", // 41
+ "null_type", // 42
+ "undefined_type", // 43
+ "enum_literal_type", // 44
+ // Pointer types (45-51)
+ "ptr_usize_type", // 45
+ "ptr_const_comptime_int_type", // 46
+ "manyptr_u8_type", // 47
+ "manyptr_const_u8_type", // 48
+ "manyptr_const_u8_sentinel_0_type", // 49
+ "slice_const_u8_type", // 50
+ "slice_const_u8_sentinel_0_type", // 51
+ // Vector types (52-98)
+ "vector_8_i8_type", // 52
+ "vector_16_i8_type", // 53
+ "vector_32_i8_type", // 54
+ "vector_64_i8_type", // 55
+ "vector_1_u8_type", // 56
+ "vector_2_u8_type", // 57
+ "vector_4_u8_type", // 58
+ "vector_8_u8_type", // 59
+ "vector_16_u8_type", // 60
+ "vector_32_u8_type", // 61
+ "vector_64_u8_type", // 62
+ "vector_2_i16_type", // 63
+ "vector_4_i16_type", // 64
+ "vector_8_i16_type", // 65
+ "vector_16_i16_type", // 66
+ "vector_32_i16_type", // 67
+ "vector_4_u16_type", // 68
+ "vector_8_u16_type", // 69
+ "vector_16_u16_type", // 70
+ "vector_32_u16_type", // 71
+ "vector_2_i32_type", // 72
+ "vector_4_i32_type", // 73
+ "vector_8_i32_type", // 74
+ "vector_16_i32_type", // 75
+ "vector_4_u32_type", // 76
+ "vector_8_u32_type", // 77
+ "vector_16_u32_type", // 78
+ "vector_2_i64_type", // 79
+ "vector_4_i64_type", // 80
+ "vector_8_i64_type", // 81
+ "vector_2_u64_type", // 82
+ "vector_4_u64_type", // 83
+ "vector_8_u64_type", // 84
+ "vector_1_u128_type", // 85
+ "vector_2_u128_type", // 86
+ "vector_1_u256_type", // 87
+ "vector_4_f16_type", // 88
+ "vector_8_f16_type", // 89
+ "vector_16_f16_type", // 90
+ "vector_32_f16_type", // 91
+ "vector_2_f32_type", // 92
+ "vector_4_f32_type", // 93
+ "vector_8_f32_type", // 94
+ "vector_16_f32_type", // 95
+ "vector_2_f64_type", // 96
+ "vector_4_f64_type", // 97
+ "vector_8_f64_type", // 98
+ // More types (99-103)
+ "optional_noreturn_type", // 99
+ "anyerror_void_error_union_type", // 100
+ "adhoc_inferred_error_set_type", // 101
+ "generic_poison_type", // 102
+ "empty_tuple_type", // 103
+ // Values (104-123)
+ "undef", // 104
+ "undef_bool", // 105
+ "undef_usize", // 106
+ "undef_u1", // 107
+ "zero", // 108
+ "zero_usize", // 109
+ "zero_u1", // 110
+ "zero_u8", // 111
+ "one", // 112
+ "one_usize", // 113
+ "one_u1", // 114
+ "one_u8", // 115
+ "four_u8", // 116
+ "negative_one", // 117
+ "void_value", // 118
+ "unreachable_value", // 119
+ "null_value", // 120
+ "bool_true", // 121
+ "bool_false", // 122
+ "empty_tuple", // 123
+};
+
+// --- SimpleType name table ---
+
+static const char* const simple_type_names[] = {
+ "f16", // 0
+ "f32", // 1
+ "f64", // 2
+ "f80", // 3
+ "f128", // 4
+ "usize", // 5
+ "isize", // 6
+ "c_char", // 7
+ "c_short", // 8
+ "c_ushort", // 9
+ "c_int", // 10
+ "c_uint", // 11
+ "c_long", // 12
+ "c_ulong", // 13
+ "c_longlong", // 14
+ "c_ulonglong", // 15
+ "c_longdouble", // 16
+ "anyopaque", // 17
+ "bool", // 18
+ "void", // 19
+ "type", // 20
+ "anyerror", // 21
+ "comptime_int", // 22
+ "comptime_float", // 23
+ "noreturn", // 24
+ "null", // 25
+ "undefined", // 26
+ "enum_literal", // 27
+ "adhoc_inferred_error_set", // 28
+ "generic_poison", // 29
+};
+
+#define SIMPLE_TYPE_COUNT 30
+
+// --- Forward declarations ---
+
+static void writeValue(
+ FILE* out, const InternPool* ip, InternPoolIndex val_ip);
+static void writeInstRef(
+ FILE* out, const Air* air, const InternPool* ip, AirInstRef ref);
+static void writeBody(FILE* out, const Air* air, const InternPool* ip,
+ const uint32_t* body, uint32_t body_len, uint32_t indent);
+
+// --- Type printing ---
+// Dispatch on InternPoolKey.tag to print a human-readable type.
+
+static void writeType(FILE* out, const InternPool* ip, InternPoolIndex ty_ip) {
+ if (ty_ip == IP_INDEX_NONE) {
+ fprintf(out, "(unknown)");
+ return;
+ }
+ InternPoolKey key = ipIndexToKey(ip, ty_ip);
+ switch (key.tag) {
+ case IP_KEY_INT_TYPE:
+ fprintf(out, "%c%" PRIu16,
+ key.data.int_type.signedness ? 'i' : 'u',
+ key.data.int_type.bits);
+ break;
+ case IP_KEY_SIMPLE_TYPE:
+ if ((uint32_t)key.data.simple_type < SIMPLE_TYPE_COUNT)
+ fprintf(out, "%s", simple_type_names[key.data.simple_type]);
+ else
+ fprintf(out, "(simple_type@%u)", (unsigned)key.data.simple_type);
+ break;
+ case IP_KEY_PTR_TYPE: {
+ uint32_t size = key.data.ptr_type.flags & PTR_FLAGS_SIZE_MASK;
+ bool is_const = (key.data.ptr_type.flags & PTR_FLAGS_IS_CONST) != 0;
+ InternPoolIndex sentinel = key.data.ptr_type.sentinel;
+ switch (size) {
+ case PTR_FLAGS_SIZE_ONE:
+ fprintf(out, "*");
+ break;
+ case PTR_FLAGS_SIZE_MANY:
+ if (sentinel != IP_INDEX_NONE) {
+ fprintf(out, "[*:");
+ writeValue(out, ip, sentinel);
+ fprintf(out, "]");
+ } else {
+ fprintf(out, "[*]");
+ }
+ break;
+ case PTR_FLAGS_SIZE_SLICE:
+ if (sentinel != IP_INDEX_NONE) {
+ fprintf(out, "[:");
+ writeValue(out, ip, sentinel);
+ fprintf(out, "]");
+ } else {
+ fprintf(out, "[]");
+ }
+ break;
+ case PTR_FLAGS_SIZE_C:
+ fprintf(out, "[*c]");
+ break;
+ }
+ if (is_const)
+ fprintf(out, "const ");
+ writeType(out, ip, key.data.ptr_type.child);
+ break;
+ }
+ case IP_KEY_FUNC_TYPE:
+ fprintf(out, "fn (");
+ for (uint32_t i = 0; i < key.data.func_type.param_count; i++) {
+ if (i > 0)
+ fprintf(out, ", ");
+ fprintf(out, "...");
+ }
+ fprintf(out, ") ");
+ writeType(out, ip, key.data.func_type.return_type);
+ break;
+ case IP_KEY_OPT_TYPE:
+ fprintf(out, "?");
+ writeType(out, ip, key.data.opt_type);
+ break;
+ case IP_KEY_ANYFRAME_TYPE:
+ if (key.data.anyframe_type != IP_INDEX_NONE) {
+ fprintf(out, "anyframe->");
+ writeType(out, ip, key.data.anyframe_type);
+ } else {
+ fprintf(out, "anyframe");
+ }
+ break;
+ case IP_KEY_ARRAY_TYPE:
+ fprintf(out, "[%" PRIu64 "]", key.data.array_type.len);
+ writeType(out, ip, key.data.array_type.child);
+ break;
+ case IP_KEY_VECTOR_TYPE:
+ fprintf(out, "@Vector(%" PRIu32 ", ", key.data.vector_type.len);
+ writeType(out, ip, key.data.vector_type.child);
+ fprintf(out, ")");
+ break;
+ case IP_KEY_ERROR_UNION_TYPE:
+ writeType(out, ip, key.data.error_union_type.error_set);
+ fprintf(out, "!");
+ writeType(out, ip, key.data.error_union_type.payload);
+ break;
+ case IP_KEY_TUPLE_TYPE:
+ fprintf(out, "struct{}");
+ break;
+ default:
+ fprintf(out, "(type@%u)", ty_ip);
+ break;
+ }
+}
+
+// --- Value printing ---
+// Dispatch on InternPoolKey.tag to print a human-readable value.
+
+static void writeValue(
+ FILE* out, const InternPool* ip, InternPoolIndex val_ip) {
+ InternPoolKey key = ipIndexToKey(ip, val_ip);
+ switch (key.tag) {
+ case IP_KEY_INT:
+ if (key.data.int_val.is_negative)
+ fprintf(out, "-");
+ fprintf(out, "%" PRIu64, key.data.int_val.value);
+ break;
+ case IP_KEY_SIMPLE_VALUE:
+ switch (key.data.simple_value) {
+ case SIMPLE_VALUE_VOID:
+ fprintf(out, "{}");
+ break;
+ case SIMPLE_VALUE_NULL:
+ fprintf(out, "null");
+ break;
+ case SIMPLE_VALUE_UNDEFINED:
+ fprintf(out, "undefined");
+ break;
+ case SIMPLE_VALUE_TRUE:
+ fprintf(out, "true");
+ break;
+ case SIMPLE_VALUE_FALSE:
+ fprintf(out, "false");
+ break;
+ case SIMPLE_VALUE_UNREACHABLE:
+ fprintf(out, "unreachable");
+ break;
+ case SIMPLE_VALUE_EMPTY_TUPLE:
+ fprintf(out, ".{}");
+ break;
+ }
+ break;
+ case IP_KEY_UNDEF:
+ fprintf(out, "undefined");
+ break;
+ case IP_KEY_FUNC:
+ fprintf(out, "(function)");
+ break;
+ case IP_KEY_FLOAT:
+ fprintf(out, "%g", key.data.float_val);
+ break;
+ default:
+ fprintf(out, "(val@%u)", val_ip);
+ break;
+ }
+}
+
+// --- InstRef printing ---
+// Ported from print.zig writeInstRef / writeOperand.
+
+static void writeInstRef(
+ FILE* out, const Air* air, const InternPool* ip, AirInstRef ref) {
+ (void)air;
+ if (ref == AIR_REF_NONE) {
+ fprintf(out, "none");
+ } else if (AIR_REF_IS_INST(ref)) {
+ fprintf(out, "%%%" PRIu32, AIR_REF_TO_INST(ref));
+ } else {
+ // IP ref
+ uint32_t idx = AIR_REF_TO_IP(ref);
+ if (idx < IP_INDEX_PREINTERN_COUNT) {
+ fprintf(out, "@.%s", ip_static_names[idx]);
+ } else {
+ // Print <type, value>
+ InternPoolIndex ty = ipTypeOf(ip, idx);
+ fprintf(out, "<");
+ writeType(out, ip, ty);
+ fprintf(out, ", ");
+ writeValue(out, ip, idx);
+ fprintf(out, ">");
+ }
+ }
+}
+
+// --- Instruction index printing (for br/repeat targets) ---
+
+static void writeInstIndex(FILE* out, uint32_t inst) {
+ fprintf(out, "%%%" PRIu32, inst);
+}
+
+// --- Indent helper ---
+
+static void writeIndent(FILE* out, uint32_t indent) {
+ for (uint32_t i = 0; i < indent; i++)
+ fputc(' ', out);
+}
+
+// --- Instruction operand writers ---
+// Each follows the upstream print.zig pattern.
+
+static void writeBinOp(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef lhs = air->inst_datas[inst].bin_op.lhs;
+ AirInstRef rhs = air->inst_datas[inst].bin_op.rhs;
+ writeInstRef(out, air, ip, lhs);
+ fprintf(out, ", ");
+ writeInstRef(out, air, ip, rhs);
+}
+
+static void writeUnOp(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef operand = air->inst_datas[inst].un_op.operand;
+ writeInstRef(out, air, ip, operand);
+}
+
+static void writeTy(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ (void)air;
+ AirInstRef ty_ref = air->inst_datas[inst].ty.ty_ref;
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+}
+
+static void writeArg(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].arg.ty_ref;
+ uint32_t zir_param_index = air->inst_datas[inst].arg.zir_param_index;
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+ fprintf(out, ", %" PRIu32, zir_param_index);
+}
+
+static void writeTyOp(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_op.ty_ref;
+ AirInstRef operand = air->inst_datas[inst].ty_op.operand;
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+ fprintf(out, ", ");
+ writeInstRef(out, air, ip, operand);
+}
+
+static void writeBlock(FILE* out, const Air* air, const InternPool* ip,
+ uint8_t tag, uint32_t inst, uint32_t indent) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+
+ uint32_t body_offset;
+ if (tag == AIR_INST_DBG_INLINE_BLOCK) {
+ // AirDbgInlineBlock: {func, body_len, ...body}
+ InternPoolIndex func_ip_idx = air->extra[payload];
+ fprintf(out, ", ");
+ writeInstRef(out, air, ip, AIR_REF_FROM_IP(func_ip_idx));
+ uint32_t body_len = air->extra[payload + 1];
+ body_offset = payload + 2;
+ fprintf(out, ", {\n");
+ writeBody(out, air, ip, &air->extra[body_offset], body_len,
+ indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}");
+ } else {
+ // AirBlock: {body_len, ...body}
+ uint32_t body_len = air->extra[payload];
+ body_offset = payload + 1;
+ fprintf(out, ", {\n");
+ writeBody(out, air, ip, &air->extra[body_offset], body_len,
+ indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}");
+ }
+}
+
+static void writeLoop(FILE* out, const Air* air, const InternPool* ip,
+ uint32_t inst, uint32_t indent) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+ // AirBlock: {body_len, ...body}
+ uint32_t body_len = air->extra[payload];
+ uint32_t body_offset = payload + 1;
+ fprintf(out, ", {\n");
+ writeBody(out, air, ip, &air->extra[body_offset], body_len, indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}");
+}
+
+static void writeTyPlBin(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ // AirBin: {lhs, rhs}
+ AirInstRef lhs = air->extra[payload];
+ AirInstRef rhs = air->extra[payload + 1];
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+ fprintf(out, ", ");
+ writeInstRef(out, air, ip, lhs);
+ fprintf(out, ", ");
+ writeInstRef(out, air, ip, rhs);
+}
+
+static void writeCall(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef operand = air->inst_datas[inst].pl_op.operand;
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ // AirCall: {args_len, ...args}
+ uint32_t args_len = air->extra[payload];
+ writeInstRef(out, air, ip, operand);
+ fprintf(out, ", [");
+ for (uint32_t i = 0; i < args_len; i++) {
+ if (i > 0)
+ fprintf(out, ", ");
+ AirInstRef arg = air->extra[payload + 1 + i];
+ writeInstRef(out, air, ip, arg);
+ }
+ fprintf(out, "]");
+}
+
+static void writeBr(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ uint32_t block_inst = air->inst_datas[inst].br.block_inst;
+ AirInstRef operand = air->inst_datas[inst].br.operand;
+ writeInstIndex(out, block_inst);
+ fprintf(out, ", ");
+ writeInstRef(out, air, ip, operand);
+}
+
+static void writeRepeat(FILE* out, const Air* air, uint32_t inst) {
+ uint32_t loop_inst = air->inst_datas[inst].repeat_data.loop_inst;
+ writeInstIndex(out, loop_inst);
+}
+
+static void writeCondBr(FILE* out, const Air* air, const InternPool* ip,
+ uint32_t inst, uint32_t indent) {
+ AirInstRef operand = air->inst_datas[inst].pl_op.operand;
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ // AirCondBr: {then_body_len, else_body_len, ...then_body, ...else_body}
+ uint32_t then_body_len = air->extra[payload];
+ uint32_t else_body_len = air->extra[payload + 1];
+ const uint32_t* then_body = &air->extra[payload + 2];
+ const uint32_t* else_body = &air->extra[payload + 2 + then_body_len];
+
+ writeInstRef(out, air, ip, operand);
+ fprintf(out, ", {\n");
+ writeBody(out, air, ip, then_body, then_body_len, indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}, {\n");
+ writeBody(out, air, ip, else_body, else_body_len, indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}");
+}
+
+static void writeTry(FILE* out, const Air* air, const InternPool* ip,
+ uint32_t inst, uint32_t indent) {
+ AirInstRef operand = air->inst_datas[inst].pl_op.operand;
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ // AirTry: {body_len, ...body}
+ uint32_t body_len = air->extra[payload];
+ const uint32_t* body = &air->extra[payload + 1];
+
+ writeInstRef(out, air, ip, operand);
+ fprintf(out, ", {\n");
+ writeBody(out, air, ip, body, body_len, indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}");
+}
+
+static void writeTryPtr(FILE* out, const Air* air, const InternPool* ip,
+ uint32_t inst, uint32_t indent) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ // AirTryPtr: {body_len, ...body} (plus ptr from extra)
+ // Extra layout: ptr(u32), body_len, ...body
+ AirInstRef ptr = air->extra[payload];
+ uint32_t body_len = air->extra[payload + 1];
+ const uint32_t* body = &air->extra[payload + 2];
+
+ writeInstRef(out, air, ip, ptr);
+ fprintf(out, ", ");
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+ fprintf(out, ", {\n");
+ writeBody(out, air, ip, body, body_len, indent + 2);
+ writeIndent(out, indent);
+ fprintf(out, "}");
+}
+
+static void writeDbgStmt(FILE* out, const Air* air, uint32_t inst) {
+ uint32_t line = air->inst_datas[inst].dbg_stmt.line;
+ uint32_t column = air->inst_datas[inst].dbg_stmt.column;
+ // Print 1-indexed (matching upstream: line + 1, column + 1).
+ fprintf(out, "%" PRIu32 ":%" PRIu32, line + 1, column + 1);
+}
+
+static void writeDbgVar(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef operand = air->inst_datas[inst].pl_op.operand;
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ writeInstRef(out, air, ip, operand);
+ // payload is index into air->extra where the NUL-terminated name is stored.
+ const char* name = (const char*)&air->extra[payload];
+ fprintf(out, ", \"%s\"", name);
+}
+
+static void writeStructField(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ // AirStructField: {field_index} — but struct_field_ptr uses ty_pl
+ // with extra containing operand + field_index.
+ // Actually: upstream StructField has struct_operand + field_index.
+ // In C implementation, ty_pl.payload points to extra with {field_index}.
+ // The struct operand is in extra[payload] and field_index in
+ // extra[payload+1] (matching AirStructField layout).
+ (void)ty_ref;
+ AirInstRef struct_operand = air->extra[payload];
+ uint32_t field_index = air->extra[payload + 1];
+ writeInstRef(out, air, ip, struct_operand);
+ fprintf(out, ", %" PRIu32, field_index);
+}
+
+static void writeAggregateInit(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ writeType(out, ip, AIR_REF_TO_IP(ty_ref));
+ // Elements are stored directly starting at payload.
+ // We don't know the count without the type, so print a TODO.
+ fprintf(out, ", [TODO:aggregate_init@%" PRIu32 "]", payload);
+}
+
+static void writeUnionInit(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ (void)ty_ref;
+ uint32_t field_index = air->extra[payload];
+ AirInstRef init_ref = air->extra[payload + 1];
+ fprintf(out, "%" PRIu32 ", ", field_index);
+ writeInstRef(out, air, ip, init_ref);
+}
+
+static void writeFieldParentPtr(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef ty_ref = air->inst_datas[inst].ty_pl.ty_ref;
+ uint32_t payload = air->inst_datas[inst].ty_pl.payload;
+ (void)ty_ref;
+ // AirFieldParentPtr: {field_owner, field_index}
+ AirInstRef field_ptr = air->extra[payload];
+ uint32_t field_index = air->extra[payload + 1];
+ writeInstRef(out, air, ip, field_ptr);
+ fprintf(out, ", %" PRIu32, field_index);
+}
+
+static void writeWasmMemorySize(FILE* out, const Air* air, uint32_t inst) {
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ fprintf(out, "%" PRIu32, payload);
+}
+
+static void writeWasmMemoryGrow(
+ FILE* out, const Air* air, const InternPool* ip, uint32_t inst) {
+ AirInstRef operand = air->inst_datas[inst].pl_op.operand;
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ fprintf(out, "%" PRIu32 ", ", payload);
+ writeInstRef(out, air, ip, operand);
+}
+
+static void writeWorkDimension(FILE* out, const Air* air, uint32_t inst) {
+ uint32_t payload = air->inst_datas[inst].pl_op.payload;
+ fprintf(out, "%" PRIu32, payload);
+}
+
+// --- Main instruction dispatch ---
+// Ported from print.zig writeInst (lines 104-341).
+
+static void writeInst(FILE* out, const Air* air, const InternPool* ip,
+ uint32_t inst, uint32_t indent) {
+ uint8_t tag = air->inst_tags[inst];
+
+ writeIndent(out, indent);
+ fprintf(out, "%%%" PRIu32 " = ", inst);
+ writeTagName(out, tag);
+ fprintf(out, "(");
+
+ switch (tag) {
+ // --- bin_op group ---
+ case AIR_INST_ADD:
+ case AIR_INST_ADD_SAFE:
+ case AIR_INST_ADD_OPTIMIZED:
+ case AIR_INST_ADD_WRAP:
+ case AIR_INST_ADD_SAT:
+ case AIR_INST_SUB:
+ case AIR_INST_SUB_SAFE:
+ case AIR_INST_SUB_OPTIMIZED:
+ case AIR_INST_SUB_WRAP:
+ case AIR_INST_SUB_SAT:
+ case AIR_INST_MUL:
+ case AIR_INST_MUL_SAFE:
+ case AIR_INST_MUL_OPTIMIZED:
+ case AIR_INST_MUL_WRAP:
+ case AIR_INST_MUL_SAT:
+ case AIR_INST_DIV_FLOAT:
+ case AIR_INST_DIV_FLOAT_OPTIMIZED:
+ case AIR_INST_DIV_TRUNC:
+ case AIR_INST_DIV_TRUNC_OPTIMIZED:
+ case AIR_INST_DIV_FLOOR:
+ case AIR_INST_DIV_FLOOR_OPTIMIZED:
+ case AIR_INST_DIV_EXACT:
+ case AIR_INST_DIV_EXACT_OPTIMIZED:
+ case AIR_INST_REM:
+ case AIR_INST_REM_OPTIMIZED:
+ case AIR_INST_MOD:
+ case AIR_INST_MOD_OPTIMIZED:
+ case AIR_INST_BIT_AND:
+ case AIR_INST_BIT_OR:
+ case AIR_INST_XOR:
+ case AIR_INST_CMP_LT:
+ case AIR_INST_CMP_LT_OPTIMIZED:
+ case AIR_INST_CMP_LTE:
+ case AIR_INST_CMP_LTE_OPTIMIZED:
+ case AIR_INST_CMP_EQ:
+ case AIR_INST_CMP_EQ_OPTIMIZED:
+ case AIR_INST_CMP_GTE:
+ case AIR_INST_CMP_GTE_OPTIMIZED:
+ case AIR_INST_CMP_GT:
+ case AIR_INST_CMP_GT_OPTIMIZED:
+ case AIR_INST_CMP_NEQ:
+ case AIR_INST_CMP_NEQ_OPTIMIZED:
+ case AIR_INST_BOOL_AND:
+ case AIR_INST_BOOL_OR:
+ case AIR_INST_STORE:
+ case AIR_INST_STORE_SAFE:
+ case AIR_INST_ARRAY_ELEM_VAL:
+ case AIR_INST_SLICE_ELEM_VAL:
+ case AIR_INST_PTR_ELEM_VAL:
+ case AIR_INST_SHL:
+ case AIR_INST_SHL_EXACT:
+ case AIR_INST_SHL_SAT:
+ case AIR_INST_SHR:
+ case AIR_INST_SHR_EXACT:
+ case AIR_INST_SET_UNION_TAG:
+ case AIR_INST_MIN:
+ case AIR_INST_MAX:
+ case AIR_INST_MEMCPY:
+ case AIR_INST_MEMMOVE:
+ case AIR_INST_MEMSET:
+ case AIR_INST_MEMSET_SAFE:
+ writeBinOp(out, air, ip, inst);
+ break;
+
+ // --- un_op group ---
+ case AIR_INST_IS_NULL:
+ case AIR_INST_IS_NON_NULL:
+ case AIR_INST_IS_NULL_PTR:
+ case AIR_INST_IS_NON_NULL_PTR:
+ case AIR_INST_IS_ERR:
+ case AIR_INST_IS_NON_ERR:
+ case AIR_INST_IS_ERR_PTR:
+ case AIR_INST_IS_NON_ERR_PTR:
+ case AIR_INST_RET:
+ case AIR_INST_RET_SAFE:
+ case AIR_INST_RET_LOAD:
+ case AIR_INST_IS_NAMED_ENUM_VALUE:
+ case AIR_INST_TAG_NAME:
+ case AIR_INST_ERROR_NAME:
+ case AIR_INST_SQRT:
+ case AIR_INST_SIN:
+ case AIR_INST_COS:
+ case AIR_INST_TAN:
+ case AIR_INST_EXP:
+ case AIR_INST_EXP2:
+ case AIR_INST_LOG:
+ case AIR_INST_LOG2:
+ case AIR_INST_LOG10:
+ case AIR_INST_FLOOR:
+ case AIR_INST_CEIL:
+ case AIR_INST_ROUND:
+ case AIR_INST_TRUNC_FLOAT:
+ case AIR_INST_NEG:
+ case AIR_INST_NEG_OPTIMIZED:
+ case AIR_INST_CMP_LT_ERRORS_LEN:
+ case AIR_INST_SET_ERR_RETURN_TRACE:
+ case AIR_INST_C_VA_END:
+ writeUnOp(out, air, ip, inst);
+ break;
+
+ // --- no_op group ---
+ case AIR_INST_TRAP:
+ case AIR_INST_BREAKPOINT:
+ case AIR_INST_DBG_EMPTY_STMT:
+ case AIR_INST_UNREACH:
+ case AIR_INST_RET_ADDR:
+ case AIR_INST_FRAME_ADDR:
+ case AIR_INST_SAVE_ERR_RETURN_TRACE_INDEX:
+ // no operands
+ break;
+
+ // --- ty group ---
+ case AIR_INST_ALLOC:
+ case AIR_INST_RET_PTR:
+ case AIR_INST_ERR_RETURN_TRACE:
+ case AIR_INST_C_VA_START:
+ writeTy(out, air, ip, inst);
+ break;
+
+ // --- arg ---
+ case AIR_INST_ARG:
+ writeArg(out, air, ip, inst);
+ break;
+
+ // --- ty_op group ---
+ case AIR_INST_NOT:
+ case AIR_INST_BITCAST:
+ case AIR_INST_LOAD:
+ case AIR_INST_FPTRUNC:
+ case AIR_INST_FPEXT:
+ case AIR_INST_INTCAST:
+ case AIR_INST_INTCAST_SAFE:
+ case AIR_INST_TRUNC:
+ case AIR_INST_OPTIONAL_PAYLOAD:
+ case AIR_INST_OPTIONAL_PAYLOAD_PTR:
+ case AIR_INST_OPTIONAL_PAYLOAD_PTR_SET:
+ case AIR_INST_ERRUNION_PAYLOAD_PTR_SET:
+ case AIR_INST_WRAP_OPTIONAL:
+ case AIR_INST_UNWRAP_ERRUNION_PAYLOAD:
+ case AIR_INST_UNWRAP_ERRUNION_ERR:
+ case AIR_INST_UNWRAP_ERRUNION_PAYLOAD_PTR:
+ case AIR_INST_UNWRAP_ERRUNION_ERR_PTR:
+ case AIR_INST_WRAP_ERRUNION_PAYLOAD:
+ case AIR_INST_WRAP_ERRUNION_ERR:
+ case AIR_INST_SLICE_PTR:
+ case AIR_INST_SLICE_LEN:
+ case AIR_INST_PTR_SLICE_LEN_PTR:
+ case AIR_INST_PTR_SLICE_PTR_PTR:
+ case AIR_INST_STRUCT_FIELD_PTR_INDEX_0:
+ case AIR_INST_STRUCT_FIELD_PTR_INDEX_1:
+ case AIR_INST_STRUCT_FIELD_PTR_INDEX_2:
+ case AIR_INST_STRUCT_FIELD_PTR_INDEX_3:
+ case AIR_INST_ARRAY_TO_SLICE:
+ case AIR_INST_FLOAT_FROM_INT:
+ case AIR_INST_SPLAT:
+ case AIR_INST_INT_FROM_FLOAT:
+ case AIR_INST_INT_FROM_FLOAT_OPTIMIZED:
+ case AIR_INST_INT_FROM_FLOAT_SAFE:
+ case AIR_INST_INT_FROM_FLOAT_OPTIMIZED_SAFE:
+ case AIR_INST_GET_UNION_TAG:
+ case AIR_INST_CLZ:
+ case AIR_INST_CTZ:
+ case AIR_INST_POPCOUNT:
+ case AIR_INST_BYTE_SWAP:
+ case AIR_INST_BIT_REVERSE:
+ case AIR_INST_ABS:
+ case AIR_INST_ERROR_SET_HAS_VALUE:
+ case AIR_INST_ADDRSPACE_CAST:
+ case AIR_INST_C_VA_ARG:
+ case AIR_INST_C_VA_COPY:
+ writeTyOp(out, air, ip, inst);
+ break;
+
+ // --- block / dbg_inline_block ---
+ case AIR_INST_BLOCK:
+ case AIR_INST_DBG_INLINE_BLOCK:
+ writeBlock(out, air, ip, tag, inst, indent);
+ break;
+
+ // --- loop ---
+ case AIR_INST_LOOP:
+ writeLoop(out, air, ip, inst, indent);
+ break;
+
+ // --- ty_pl_bin group ---
+ case AIR_INST_SLICE:
+ case AIR_INST_SLICE_ELEM_PTR:
+ case AIR_INST_PTR_ELEM_PTR:
+ case AIR_INST_PTR_ADD:
+ case AIR_INST_PTR_SUB:
+ case AIR_INST_ADD_WITH_OVERFLOW:
+ case AIR_INST_SUB_WITH_OVERFLOW:
+ case AIR_INST_MUL_WITH_OVERFLOW:
+ case AIR_INST_SHL_WITH_OVERFLOW:
+ writeTyPlBin(out, air, ip, inst);
+ break;
+
+ // --- call group ---
+ case AIR_INST_CALL:
+ case AIR_INST_CALL_ALWAYS_TAIL:
+ case AIR_INST_CALL_NEVER_TAIL:
+ case AIR_INST_CALL_NEVER_INLINE:
+ writeCall(out, air, ip, inst);
+ break;
+
+ // --- dbg_var group ---
+ case AIR_INST_DBG_VAR_PTR:
+ case AIR_INST_DBG_VAR_VAL:
+ case AIR_INST_DBG_ARG_INLINE:
+ writeDbgVar(out, air, ip, inst);
+ break;
+
+ // --- struct_field_ptr / struct_field_val ---
+ case AIR_INST_STRUCT_FIELD_PTR:
+ case AIR_INST_STRUCT_FIELD_VAL:
+ writeStructField(out, air, ip, inst);
+ break;
+
+ // --- inferred_alloc ---
+ case AIR_INST_INFERRED_ALLOC:
+ case AIR_INST_INFERRED_ALLOC_COMPTIME:
+ fprintf(out, "TODO");
+ break;
+
+ // --- assembly ---
+ case AIR_INST_ASSEMBLY:
+ fprintf(out, "TODO");
+ break;
+
+ // --- dbg_stmt ---
+ case AIR_INST_DBG_STMT:
+ writeDbgStmt(out, air, inst);
+ break;
+
+ // --- aggregate_init ---
+ case AIR_INST_AGGREGATE_INIT:
+ writeAggregateInit(out, air, ip, inst);
+ break;
+
+ // --- union_init ---
+ case AIR_INST_UNION_INIT:
+ writeUnionInit(out, air, ip, inst);
+ break;
+
+ // --- br / switch_dispatch ---
+ case AIR_INST_BR:
+ case AIR_INST_SWITCH_DISPATCH:
+ writeBr(out, air, ip, inst);
+ break;
+
+ // --- repeat ---
+ case AIR_INST_REPEAT:
+ writeRepeat(out, air, inst);
+ break;
+
+ // --- cond_br ---
+ case AIR_INST_COND_BR:
+ writeCondBr(out, air, ip, inst, indent);
+ break;
+
+ // --- try / try_cold ---
+ case AIR_INST_TRY:
+ case AIR_INST_TRY_COLD:
+ writeTry(out, air, ip, inst, indent);
+ break;
+
+ // --- try_ptr / try_ptr_cold ---
+ case AIR_INST_TRY_PTR:
+ case AIR_INST_TRY_PTR_COLD:
+ writeTryPtr(out, air, ip, inst, indent);
+ break;
+
+ // --- switch_br / loop_switch_br ---
+ case AIR_INST_SWITCH_BR:
+ case AIR_INST_LOOP_SWITCH_BR:
+ fprintf(out, "TODO");
+ break;
+
+ // --- cmpxchg ---
+ case AIR_INST_CMPXCHG_WEAK:
+ case AIR_INST_CMPXCHG_STRONG:
+ fprintf(out, "TODO");
+ break;
+
+ // --- atomic_load ---
+ case AIR_INST_ATOMIC_LOAD:
+ fprintf(out, "TODO");
+ break;
+
+ // --- prefetch ---
+ case AIR_INST_PREFETCH:
+ fprintf(out, "TODO");
+ break;
+
+ // --- atomic_store ---
+ case AIR_INST_ATOMIC_STORE_UNORDERED:
+ case AIR_INST_ATOMIC_STORE_MONOTONIC:
+ case AIR_INST_ATOMIC_STORE_RELEASE:
+ case AIR_INST_ATOMIC_STORE_SEQ_CST:
+ writeBinOp(out, air, ip, inst);
+ break;
+
+ // --- atomic_rmw ---
+ case AIR_INST_ATOMIC_RMW:
+ fprintf(out, "TODO");
+ break;
+
+ // --- field_parent_ptr ---
+ case AIR_INST_FIELD_PARENT_PTR:
+ writeFieldParentPtr(out, air, ip, inst);
+ break;
+
+ // --- wasm ---
+ case AIR_INST_WASM_MEMORY_SIZE:
+ writeWasmMemorySize(out, air, inst);
+ break;
+ case AIR_INST_WASM_MEMORY_GROW:
+ writeWasmMemoryGrow(out, air, ip, inst);
+ break;
+
+ // --- mul_add ---
+ case AIR_INST_MUL_ADD:
+ fprintf(out, "TODO");
+ break;
+
+ // --- select ---
+ case AIR_INST_SELECT:
+ fprintf(out, "TODO");
+ break;
+
+ // --- shuffle ---
+ case AIR_INST_SHUFFLE_ONE:
+ case AIR_INST_SHUFFLE_TWO:
+ fprintf(out, "TODO");
+ break;
+
+ // --- reduce ---
+ case AIR_INST_REDUCE:
+ case AIR_INST_REDUCE_OPTIMIZED:
+ fprintf(out, "TODO");
+ break;
+
+ // --- cmp_vector ---
+ case AIR_INST_CMP_VECTOR:
+ case AIR_INST_CMP_VECTOR_OPTIMIZED:
+ fprintf(out, "TODO");
+ break;
+
+ // --- vector_store_elem ---
+ case AIR_INST_VECTOR_STORE_ELEM:
+ fprintf(out, "TODO");
+ break;
+
+ // --- runtime_nav_ptr ---
+ case AIR_INST_RUNTIME_NAV_PTR:
+ fprintf(out, "TODO");
+ break;
+
+ // --- work dimensions ---
+ case AIR_INST_WORK_ITEM_ID:
+ case AIR_INST_WORK_GROUP_SIZE:
+ case AIR_INST_WORK_GROUP_ID:
+ writeWorkDimension(out, air, inst);
+ break;
+ }
+
+ fprintf(out, ")\n");
+}
+
+// --- Body writer ---
+// Iterates over instruction indices and prints each.
+
+static void writeBody(FILE* out, const Air* air, const InternPool* ip,
+ const uint32_t* body, uint32_t body_len, uint32_t indent) {
+ for (uint32_t i = 0; i < body_len; i++) {
+ writeInst(out, air, ip, body[i], indent);
+ }
+}
+
+// --- Main entry point ---
+
+void verboseAirPrint(
+ FILE* out, const SemaFuncAirList* list, const InternPool* ip) {
+ for (uint32_t f = 0; f < list->len; f++) {
+ const SemaFuncAir* entry = &list->items[f];
+ const Air* air = &entry->air;
+ const char* name = entry->name ? entry->name : "(unnamed)";
+
+ // Compute byte sizes matching upstream format.
+ uint32_t inst_bytes
+ = air->inst_len * (uint32_t)(sizeof(uint8_t) + sizeof(AirInstData));
+ uint32_t extra_bytes = air->extra_len * (uint32_t)sizeof(uint32_t);
+ uint32_t total_bytes = (uint32_t)sizeof(Air) + inst_bytes + extra_bytes;
+
+ fprintf(out, "# Begin Function AIR: %s:\n", name);
+ fprintf(out, "# Total AIR bytes: %uB\n", total_bytes);
+ fprintf(out, "# AIR Instructions: %u (%uB)\n",
+ air->inst_len, inst_bytes);
+ fprintf(out, "# AIR Extra Data: %u (%uB)\n",
+ air->extra_len, extra_bytes);
+
+ // Extract main body from extra[0].
+ if (air->extra_len > 0) {
+ uint32_t main_block_offset = air->extra[0];
+ if (main_block_offset < air->extra_len) {
+ uint32_t body_len = air->extra[main_block_offset];
+ const uint32_t* body = &air->extra[main_block_offset + 1];
+ writeBody(out, air, ip, body, body_len, 2);
+ }
+ }
+
+ fprintf(out, "# End Function AIR: %s\n\n", name);
+ }
+}
diff --git a/stage0/verbose_air.h b/stage0/verbose_air.h
@@ -0,0 +1,13 @@
+// verbose_air.h — Human-readable AIR printer for debugging.
+// Ported from src/Air/print.zig.
+#ifndef _ZIG0_VERBOSE_AIR_H__
+#define _ZIG0_VERBOSE_AIR_H__
+
+#include "intern_pool.h"
+#include "sema.h"
+#include <stdio.h>
+
+void verboseAirPrint(
+ FILE* out, const SemaFuncAirList* list, const InternPool* ip);
+
+#endif
diff --git a/stage0/zig0.c b/stage0/zig0.c
@@ -2,6 +2,7 @@
#include "astgen.h"
#include "intern_pool.h"
#include "sema.h"
+#include "verbose_air.h"
#include "zir.h"
#include <stdbool.h>
@@ -13,7 +14,7 @@
// - code = 0: program successfully terminated.
// - code = 1: panicked, panic message in msg. Caller should free msg.
// - code = 2: interpreter error, error in msg. Caller should free msg.
-static int zig0Run(const char* program, char** msg) {
+static int zig0Run(const char* program, bool verbose_air, char** msg) {
uint32_t len = (uint32_t)strlen(program);
Ast ast = astParse(program, len);
if (ast.has_error) {
@@ -43,6 +44,8 @@ static int zig0Run(const char* program, char** msg) {
InternPool ip = ipInit();
Sema sema = semaInit(&ip, zir);
SemaFuncAirList func_airs = semaAnalyze(&sema);
+ if (verbose_air)
+ verboseAirPrint(stderr, &func_airs, &ip);
semaDeinit(&sema);
semaFuncAirListDeinit(&func_airs);
ipDeinit(&ip);
@@ -52,7 +55,7 @@ static int zig0Run(const char* program, char** msg) {
// API: run and:
// code = 3: abnormal error, expect something in stderr.
-int zig0RunFile(const char* fname, char** msg) {
+int zig0RunFile(const char* fname, bool verbose_air, char** msg) {
FILE* f = fopen(fname, "r");
if (f == NULL) {
perror("fopen");
@@ -89,7 +92,7 @@ int zig0RunFile(const char* fname, char** msg) {
fclose(f);
program[fsize] = 0;
- int code = zig0Run(program, msg);
+ int code = zig0Run(program, verbose_air, msg);
free(program);
return code;
}