From a787650d546b63b0dae11965904cb2cdf044cbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 20 Feb 2026 08:10:57 +0000 Subject: [PATCH] verbose_air: use caller-provided error buffer instead of heap allocation Replace the heap-allocated error_msg with a fixed-size caller-provided buffer, making error reporting infallible. All error paths now write into the buffer via bufPrint with truncation on overflow. Co-Authored-By: Claude Opus 4.6 --- src/verbose_air.zig | 64 ++++++++++++++++++++++++++++---------------- stage0/dump.h | 6 +++-- stage0/sema_test.zig | 10 +++---- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/verbose_air.zig b/src/verbose_air.zig index 0328c73112..736beaa724 100644 --- a/src/verbose_air.zig +++ b/src/verbose_air.zig @@ -30,20 +30,27 @@ const CSemaFuncAir = extern struct { const CompileAirResult = extern struct { items: ?[*]CSemaFuncAir, len: u32, - error_msg: ?[*:0]u8, // NULL on success, caller frees }; const AirCollector = struct { funcs: std.ArrayListUnmanaged(CSemaFuncAir) = .empty, - first_error: ?[*:0]u8 = null, + err_buf: *[err_buf_size]u8, + + fn hasError(self: *const AirCollector) bool { + return self.err_buf[0] != 0; + } fn collectFunc(ctx: *anyopaque, name: []const u8, air: *const Air) void { const self: *AirCollector = @ptrCast(@alignCast(ctx)); - self.collectFuncInner(name, air) catch {}; + self.collectFuncInner(name, air) catch |err| { + if (!self.hasError()) { + setErr(self.err_buf, "collectFunc '{s}': {s}", .{ name, @errorName(err) }); + } + }; } fn collectFuncInner(self: *AirCollector, name: []const u8, air: *const Air) !void { - if (self.first_error != null) return; + if (self.hasError()) return; const gpa = std.heap.c_allocator; const inst_len: u32 = @intCast(air.instructions.len); @@ -52,7 +59,7 @@ const AirCollector = struct { const zig_tags = air.instructions.items(.tag); const tags_copy: ?[*]u8 = if (inst_len > 0) blk: { const src = @as([*]const u8, @ptrCast(zig_tags.ptr))[0..inst_len]; - const dst = gpa.alloc(u8, inst_len) catch return; + const dst = try gpa.alloc(u8, inst_len); @memcpy(dst, src); break :blk dst.ptr; } else null; @@ -60,7 +67,7 @@ const AirCollector = struct { // Copy datas (8 bytes per instruction) const datas_byte_len = inst_len * 8; const datas_copy: ?[*]u8 = if (inst_len > 0) blk: { - const dst = gpa.alloc(u8, datas_byte_len) catch return; + const dst = try gpa.alloc(u8, datas_byte_len); const zig_datas = air.instructions.items(.data); if (@sizeOf(Air.Inst.Data) == 8) { const src = @as([*]const u8, @ptrCast(zig_datas.ptr))[0..datas_byte_len]; @@ -78,15 +85,15 @@ const AirCollector = struct { // Copy extra const extra_len: u32 = @intCast(air.extra.items.len); const extra_copy: ?[*]u32 = if (extra_len > 0) blk: { - const dst = gpa.alloc(u32, extra_len) catch return; + const dst = try gpa.alloc(u32, extra_len); @memcpy(dst, air.extra.items); break :blk dst.ptr; } else null; // Copy name - const name_copy = gpa.dupeZ(u8, name) catch return; + const name_copy = try gpa.dupeZ(u8, name); - self.funcs.append(gpa, .{ + try self.funcs.append(gpa, .{ .name = name_copy.ptr, .air = .{ .inst_len = inst_len, @@ -97,25 +104,31 @@ const AirCollector = struct { .extra_cap = extra_len, .extra = extra_copy, }, - }) catch return; + }); } }; +const err_buf_size = 256; + export fn zig_compile_air( src_path_ptr: [*:0]const u8, module_root_ptr: ?[*:0]const u8, + err_buf_ptr: [*]u8, ) CompileAirResult { + const err_buf: *[err_buf_size]u8 = err_buf_ptr[0..err_buf_size]; + err_buf[0] = 0; return zigCompileAirImpl( std.mem.span(src_path_ptr), if (module_root_ptr) |p| std.mem.span(p) else null, + err_buf, ) catch |err| { - return errResult(@errorName(err)); + setErr(err_buf, "{s}", .{@errorName(err)}); + return .{ .items = null, .len = 0 }; }; } export fn zig_compile_air_free(result: *CompileAirResult) void { const gpa = std.heap.c_allocator; - if (result.error_msg) |e| gpa.free(std.mem.span(e)); if (result.items) |items| { for (items[0..result.len]) |*f| { if (f.name) |n| gpa.free(std.mem.span(n)); @@ -127,15 +140,18 @@ export fn zig_compile_air_free(result: *CompileAirResult) void { } } -fn errResult(msg: []const u8) CompileAirResult { - const duped = std.heap.c_allocator.dupeZ(u8, msg) catch - return .{ .items = null, .len = 0, .error_msg = null }; - return .{ .items = null, .len = 0, .error_msg = duped.ptr }; +fn setErr(buf: *[err_buf_size]u8, comptime fmt: []const u8, args: anytype) void { + const written = std.fmt.bufPrint(buf[0 .. err_buf_size - 1], fmt, args) catch { + buf[err_buf_size - 1] = 0; + return; + }; + buf[written.len] = 0; } fn zigCompileAirImpl( src_path: []const u8, module_root_opt: ?[]const u8, + err_buf: *[err_buf_size]u8, ) !CompileAirResult { const gpa = std.heap.c_allocator; @@ -224,7 +240,7 @@ fn zigCompileAirImpl( gpa.destroy(thread_pool); } - var collector: AirCollector = .{}; + var collector: AirCollector = .{ .err_buf = err_buf }; var create_diag: Compilation.CreateDiagnostic = undefined; const comp = Compilation.create(gpa, arena, &create_diag, .{ @@ -241,7 +257,8 @@ fn zigCompileAirImpl( }, }) catch |err| switch (err) { error.CreateFail => { - return errResult("Compilation.create failed"); + setErr(err_buf, "Compilation.create failed", .{}); + return .{ .items = null, .len = 0 }; }, else => return err, }; @@ -255,11 +272,12 @@ fn zigCompileAirImpl( var buf: std.io.Writer.Allocating = .init(gpa); error_bundle.renderToWriter(.{ .ttyconf = .no_color }, &buf.writer) catch {}; defer buf.deinit(); - return errResult(buf.written()); + setErr(err_buf, "{s}", .{buf.written()}); + return .{ .items = null, .len = 0 }; } - if (collector.first_error) |e| { - return .{ .items = null, .len = 0, .error_msg = e }; + if (collector.hasError()) { + return .{ .items = null, .len = 0 }; } const allocated = collector.funcs.allocatedSlice(); @@ -271,7 +289,7 @@ fn zigCompileAirImpl( // Shrink to exact size so free works with items[0..len]. const exact = gpa.realloc(allocated, len) catch allocated; // keep original on realloc failure - return .{ .items = exact.ptr, .len = len, .error_msg = null }; + return .{ .items = exact.ptr, .len = len }; } - return .{ .items = allocated.ptr, .len = len, .error_msg = null }; + return .{ .items = allocated.ptr, .len = len }; } diff --git a/stage0/dump.h b/stage0/dump.h index efea53d365..e85d73f758 100644 --- a/stage0/dump.h +++ b/stage0/dump.h @@ -8,10 +8,12 @@ typedef struct { void* items; // SemaFuncAir* (from sema.h), owned by Zig allocator uint32_t len; - char* error_msg; // NULL on success, owned by Zig allocator } ZigCompileAirResult; -extern ZigCompileAirResult zig_compile_air(const char* src_path, const char* module_root); +#define ZIG_COMPILE_ERR_BUF_SIZE 256 + +extern ZigCompileAirResult zig_compile_air(const char* src_path, const char* module_root, + char err_buf[ZIG_COMPILE_ERR_BUF_SIZE]); extern void zig_compile_air_free(ZigCompileAirResult* result); #endif diff --git a/stage0/sema_test.zig b/stage0/sema_test.zig index db9914e46e..8eefd0ab11 100644 --- a/stage0/sema_test.zig +++ b/stage0/sema_test.zig @@ -227,9 +227,8 @@ test "sema: function decl smoke test" { const ZigCompileAirResult = extern struct { items: ?[*]c.SemaFuncAir, len: u32, - error_msg: ?[*:0]u8, }; -extern fn zig_compile_air([*:0]const u8, ?[*:0]const u8) ZigCompileAirResult; +extern fn zig_compile_air([*:0]const u8, ?[*:0]const u8, [*]u8) ZigCompileAirResult; extern fn zig_compile_air_free(*ZigCompileAirResult) void; pub fn airCompareFromSource(source: [:0]const u8, c_func_air_list: c.SemaFuncAirList) !void { @@ -249,11 +248,12 @@ pub fn airCompare( module_root: ?[*:0]const u8, c_func_air_list: c.SemaFuncAirList, ) !void { - var zig_result = zig_compile_air(src_path, module_root); + var err_buf: [c.ZIG_COMPILE_ERR_BUF_SIZE]u8 = .{0} ** c.ZIG_COMPILE_ERR_BUF_SIZE; + var zig_result = zig_compile_air(src_path, module_root, &err_buf); defer zig_compile_air_free(&zig_result); - if (zig_result.error_msg) |e| { - std.debug.print("zig_compile_air error: {s}\n", .{std.mem.span(e)}); + if (err_buf[0] != 0) { + std.debug.print("zig_compile_air error: {s}\n", .{std.mem.sliceTo(&err_buf, 0)}); return error.ZigCompileError; }