- Rework translate-c to integrate with the build system (by outputing error bundles on stdout) via --zig-integration

- Revive some of the removed cache integration logic in `cmdTranslateC` now that `translate-c` can return error bundles
- Fixup inconsistent path separators (on Windows) when building the aro include path
- Move some error bundle logic from resinator into aro.Diagnostics
- Add `ErrorBundle.addRootErrorMessageWithNotes` (extracted from resinator)
This commit is contained in:
kcbanner 2025-10-06 22:02:14 -04:00
parent 328ae41468
commit 4aa4d80ec6
6 changed files with 273 additions and 129 deletions

View File

@ -562,3 +562,79 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void {
},
}
}
const ErrorBundle = std.zig.ErrorBundle;
pub fn toErrorBundle(
d: *const Diagnostics,
gpa: std.mem.Allocator,
fail_msg: ?[]const u8,
) !ErrorBundle {
@branchHint(.cold);
var bundle: ErrorBundle.Wip = undefined;
try bundle.init(gpa);
errdefer bundle.deinit();
if (fail_msg) |msg| {
try bundle.addRootErrorMessage(.{
.msg = try bundle.addString(msg),
});
}
var cur_err: ?ErrorBundle.ErrorMessage = null;
var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
defer cur_notes.deinit(gpa);
for (d.output.to_list.messages.items) |msg| {
switch (msg.kind) {
// Clear the current error so that notes don't bleed into unassociated errors
.off, .warning => {
cur_err = null;
continue;
},
.note => if (cur_err == null) continue,
.@"fatal error", .@"error" => {},
}
const src_loc = src_loc: {
if (msg.location) |location| {
break :src_loc try bundle.addSourceLocation(.{
.src_path = try bundle.addString(location.path),
.line = location.line_no - 1, // 1-based -> 0-based
.column = location.col - 1, // 1-based -> 0-based
.span_start = location.width,
.span_main = location.width,
.span_end = location.width,
.source_line = try bundle.addString(location.line),
});
}
break :src_loc ErrorBundle.SourceLocationIndex.none;
};
switch (msg.kind) {
.@"fatal error", .@"error" => {
if (cur_err) |err| {
try bundle.addRootErrorMessageWithNotes(err, cur_notes.items);
}
cur_err = .{
.msg = try bundle.addString(msg.text),
.src_loc = src_loc,
};
cur_notes.clearRetainingCapacity();
},
.note => {
cur_err.?.notes_len += 1;
try cur_notes.append(gpa, .{
.msg = try bundle.addString(msg.text),
.src_loc = src_loc,
});
},
.off, .warning => unreachable,
}
}
if (cur_err) |err| {
try bundle.addRootErrorMessageWithNotes(err, cur_notes.items);
}
return try bundle.toOwnedBundle("");
}

View File

@ -671,7 +671,7 @@ const ErrorHandler = union(enum) {
) !void {
switch (self.*) {
.server => |*server| {
var error_bundle = try aroDiagnosticsToErrorBundle(allocator, fail_msg, comp);
var error_bundle = try comp.diagnostics.toErrorBundle(allocator, fail_msg);
defer error_bundle.deinit(allocator);
try server.serveErrorBundle(error_bundle);
@ -753,7 +753,7 @@ fn cliDiagnosticsToErrorBundle(
switch (err_details.type) {
.err => {
if (cur_err) |err| {
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
try bundle.addRootErrorMessageWithNotes(err, cur_notes.items);
}
cur_err = .{
.msg = try bundle.addString(err_details.msg.items),
@ -771,7 +771,7 @@ fn cliDiagnosticsToErrorBundle(
}
}
if (cur_err) |err| {
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
try bundle.addRootErrorMessageWithNotes(err, cur_notes.items);
}
return try bundle.toOwnedBundle("");
@ -840,7 +840,7 @@ fn diagnosticsToErrorBundle(
switch (err_details.type) {
.err => {
if (cur_err) |err| {
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
try bundle.addRootErrorMessageWithNotes(err, cur_notes.items);
}
cur_err = .{
.msg = try bundle.addString(msg_buf.written()),
@ -859,20 +859,12 @@ fn diagnosticsToErrorBundle(
}
}
if (cur_err) |err| {
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
try bundle.addRootErrorMessageWithNotes(err, cur_notes.items);
}
return try bundle.toOwnedBundle("");
}
fn flushErrorMessageIntoBundle(wip: *ErrorBundle.Wip, msg: ErrorBundle.ErrorMessage, notes: []const ErrorBundle.ErrorMessage) !void {
try wip.addRootErrorMessage(msg);
const notes_start = try wip.reserveNotes(@intCast(notes.len));
for (notes_start.., notes) |i, note| {
wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note));
}
}
fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
@branchHint(.cold);
var bundle: ErrorBundle.Wip = undefined;
@ -883,75 +875,3 @@ fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []con
});
return try bundle.toOwnedBundle("");
}
fn aroDiagnosticsToErrorBundle(
gpa: std.mem.Allocator,
fail_msg: []const u8,
comp: *aro.Compilation,
) !ErrorBundle {
@branchHint(.cold);
var bundle: ErrorBundle.Wip = undefined;
try bundle.init(gpa);
errdefer bundle.deinit();
try bundle.addRootErrorMessage(.{
.msg = try bundle.addString(fail_msg),
});
var cur_err: ?ErrorBundle.ErrorMessage = null;
var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
defer cur_notes.deinit(gpa);
for (comp.diagnostics.output.to_list.messages.items) |msg| {
switch (msg.kind) {
// Clear the current error so that notes don't bleed into unassociated errors
.off, .warning => {
cur_err = null;
continue;
},
.note => if (cur_err == null) continue,
.@"fatal error", .@"error" => {},
}
const src_loc = src_loc: {
if (msg.location) |location| {
break :src_loc try bundle.addSourceLocation(.{
.src_path = try bundle.addString(location.path),
.line = location.line_no - 1, // 1-based -> 0-based
.column = location.col - 1, // 1-based -> 0-based
.span_start = location.width,
.span_main = location.width,
.span_end = location.width,
.source_line = try bundle.addString(location.line),
});
}
break :src_loc ErrorBundle.SourceLocationIndex.none;
};
switch (msg.kind) {
.@"fatal error", .@"error" => {
if (cur_err) |err| {
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
}
cur_err = .{
.msg = try bundle.addString(msg.text),
.src_loc = src_loc,
};
cur_notes.clearRetainingCapacity();
},
.note => {
cur_err.?.notes_len += 1;
try cur_notes.append(gpa, .{
.msg = try bundle.addString(msg.text),
.src_loc = src_loc,
});
},
.off, .warning => unreachable,
}
}
if (cur_err) |err| {
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
}
return try bundle.toOwnedBundle("");
}

View File

@ -13,24 +13,33 @@ pub fn main() u8 {
const gpa = general_purpose_allocator.allocator();
defer _ = general_purpose_allocator.deinit();
var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
var arena_instance = std.heap.ArenaAllocator.init(gpa);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
const args = process.argsAlloc(arena) catch {
var args = process.argsAlloc(arena) catch {
std.debug.print("ran out of memory allocating arguments\n", .{});
if (fast_exit) process.exit(1);
return 1;
};
var zig_integration = false;
if (args.len > 1 and std.mem.eql(u8, args[1], "--zig-integration")) {
zig_integration = true;
}
var stderr_buf: [1024]u8 = undefined;
var stderr = std.fs.File.stderr().writer(&stderr_buf);
var diagnostics: aro.Diagnostics = .{
.output = .{ .to_writer = .{
var diagnostics: aro.Diagnostics = switch (zig_integration) {
false => .{ .output = .{ .to_writer = .{
.color = .detect(stderr.file),
.writer = &stderr.interface,
} },
} } },
true => .{ .output = .{ .to_list = .{
.arena = .init(gpa),
} } },
};
defer diagnostics.deinit();
var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) {
error.OutOfMemory => {
@ -47,13 +56,22 @@ pub fn main() u8 {
var toolchain: aro.Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } };
defer toolchain.deinit();
translate(&driver, &toolchain, args) catch |err| switch (err) {
translate(&driver, &toolchain, args, zig_integration) catch |err| switch (err) {
error.OutOfMemory => {
std.debug.print("ran out of memory translating\n", .{});
if (fast_exit) process.exit(1);
return 1;
},
error.FatalError => {
error.FatalError => if (zig_integration) {
serveErrorBundle(arena, &diagnostics) catch |bundle_err| {
std.debug.print("unable to serve error bundle: {}\n", .{bundle_err});
if (fast_exit) process.exit(1);
return 1;
};
if (fast_exit) process.exit(0);
return 0;
} else {
if (fast_exit) process.exit(1);
return 1;
},
@ -63,10 +81,23 @@ pub fn main() u8 {
return 1;
},
};
assert(comp.diagnostics.errors == 0 or !zig_integration);
if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0));
return @intFromBool(comp.diagnostics.errors != 0);
}
fn serveErrorBundle(arena: std.mem.Allocator, diagnostics: *const aro.Diagnostics) !void {
const error_bundle = try diagnostics.toErrorBundle(arena, "failed during translation");
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
var server: std.zig.Server = .{
.out = &stdout_writer.interface,
.in = undefined,
};
try server.serveErrorBundle(error_bundle);
}
pub const usage =
\\Usage {s}: [options] file [CC options]
\\
@ -79,7 +110,7 @@ pub const usage =
\\
;
fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void {
fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void {
const gpa = d.comp.gpa;
const aro_args = args: {
@ -99,6 +130,9 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void {
try stdout.interface.writeAll("0.0.0-dev\n");
try stdout.interface.flush();
return;
} else if (mem.eql(u8, arg, "--zig-integration")) {
if (i != 1 or !zig_integration)
return d.fatal("--zig-integration must be the first argument", .{});
} else {
i += 1;
}
@ -116,6 +150,14 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void {
return d.fatal("user provided macro source exceeded max size", .{});
}
const has_output_file = if (d.output_name) |path|
!std.mem.eql(u8, path, "-")
else
false;
if (zig_integration and !has_output_file) {
return d.fatal("--zig-integration requires specifying an output file", .{});
}
const content = try macro_buf.toOwnedSlice(gpa);
errdefer gpa.free(content);
@ -160,7 +202,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void {
defer c_tree.deinit();
if (d.diagnostics.errors != 0) {
if (fast_exit) process.exit(1);
if (fast_exit and !zig_integration) process.exit(1);
return error.FatalError;
}
@ -212,7 +254,7 @@ fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void {
if (out_writer.err) |write_err|
return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(write_err) });
if (fast_exit) process.exit(0);
if (fast_exit and !zig_integration) process.exit(0);
}
test {

View File

@ -416,6 +416,18 @@ pub const Wip = struct {
wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
}
pub fn addRootErrorMessageWithNotes(
wip: *Wip,
msg: ErrorMessage,
notes: []const ErrorMessage,
) !void {
try wip.addRootErrorMessage(msg);
const notes_start = try wip.reserveNotes(@intCast(notes.len));
for (notes_start.., notes) |i, note| {
wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note));
}
}
pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) Allocator.Error!MessageIndex {
return @enumFromInt(try addExtra(wip, em));
}

View File

@ -3398,7 +3398,7 @@ fn flush(
/// Linker backends which do not have this requirement can fall back to the simple
/// implementation at the bottom of this function.
/// This function is only called when CacheMode is `whole`.
fn renameTmpIntoCache(
pub fn renameTmpIntoCache(
cache_directory: Cache.Directory,
tmp_dir_sub_path: []const u8,
o_sub_path: []const u8,
@ -6684,7 +6684,7 @@ pub fn addTranslateCCArgs(
try argv.appendSlice(&.{ "-x", "c" });
const resource_path = try comp.dirs.zig_lib.join(arena, &.{"compiler/aro/include"});
const resource_path = try comp.dirs.zig_lib.join(arena, &.{ "compiler", "aro", "include" });
try argv.appendSlice(&.{ "-isystem", resource_path });
try comp.addCommonCCArgs(arena, argv, ext, out_dep_path, owner_mod, .aro);

View File

@ -4075,7 +4075,7 @@ fn serve(
while (true) {
const hdr = try server.receiveMessage();
// Lock the debug server while hanling the message.
// Lock the debug server while handling the message.
if (comp.debugIncremental()) ids.mutex.lock();
defer if (comp.debugIncremental()) ids.mutex.unlock();
@ -4515,49 +4515,143 @@ fn cmdTranslateC(
prog_node: std.Progress.Node,
) !void {
dev.check(.translate_c_command);
_ = file_system_inputs;
_ = fancy_output;
const color: Color = .auto;
assert(comp.c_source_files.len == 1);
const c_source_file = comp.c_source_files[0];
var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});
defer zig_cache_tmp_dir.close();
const translated_zig_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{comp.root_name});
var man: Cache.Manifest = comp.obtainCObjectCacheManifest(comp.root_mod);
man.want_shared_lock = false;
defer man.deinit();
man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
man.hash.add(comp.config.c_frontend);
Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| {
fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) });
};
if (fancy_output) |p| p.cache_hit = true;
const bin_digest, const hex_digest = if (try man.hit()) digest: {
if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
const bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest);
break :digest .{ bin_digest, hex_digest };
} else digest: {
if (fancy_output) |p| p.cache_hit = false;
const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
const cache_dir = comp.dirs.local_cache.handle;
var cache_tmp_dir = try cache_dir.makeOpenPath(tmp_sub_path, .{});
defer cache_tmp_dir.close();
const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_zig_basename });
const ext = Compilation.classifyFileExt(c_source_file.src_path);
const out_dep_path: ?[]const u8 = blk: {
if (comp.disable_c_depfile) break :blk null;
const c_src_basename = fs.path.basename(c_source_file.src_path);
const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});
const out_dep_path = try comp.tmpFilePath(arena, dep_basename);
const out_dep_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, dep_basename });
break :blk out_dep_path;
};
var argv = std.array_list.Managed([]const u8).init(arena);
try argv.append("--zig-integration");
try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path, comp.root_mod);
try argv.append(c_source_file.src_path);
try argv.appendSlice(&.{ c_source_file.src_path, "-o", translated_path });
if (comp.verbose_cc) Compilation.dump_argv(argv.items);
try translateC(comp.gpa, arena, argv.items, prog_node, null);
var stdout: []u8 = undefined;
try translateC(comp.gpa, arena, argv.items, prog_node, &stdout);
if (stdout.len > 0) {
var reader: std.Io.Reader = .fixed(stdout);
const MessageHeader = std.zig.Server.Message.Header;
const header = reader.takeStruct(MessageHeader, .little) catch unreachable;
const body = reader.take(header.bytes_len) catch unreachable;
switch (header.tag) {
.error_bundle => {
// TODO: De-dupe this logic
const EbHdr = std.zig.Server.Message.ErrorBundle;
const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
const extra_bytes =
body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
const string_bytes =
body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
const extra_array = try comp.gpa.alloc(u32, unaligned_extra.len);
@memcpy(extra_array, unaligned_extra);
const error_bundle: std.zig.ErrorBundle = .{
.string_bytes = try comp.gpa.dupe(u8, string_bytes),
.extra = extra_array,
};
if (fancy_output) |p| {
p.errors = error_bundle;
return;
} else {
error_bundle.renderToStdErr(color.renderOptions());
process.exit(1);
}
return error.AnalysisFail;
},
else => unreachable, // No other messagse are sent
}
}
if (out_dep_path) |dep_file_path| {
const dep_basename = fs.path.basename(dep_file_path);
// Add the files depended on to the cache system.
//man.addDepFilePost(zig_cache_tmp_dir, dep_basename) catch |err| switch (err) {
// error.FileNotFound => {
// // Clang didn't emit the dep file; nothing to add to the manifest.
// break :add_deps;
// },
// else => |e| return e,
//};
try man.addDepFilePost(cache_tmp_dir, dep_basename);
// Just to save disk space, we delete the file because it is never needed again.
zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| {
cache_tmp_dir.deleteFile(dep_basename) catch |err| {
warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
};
}
const bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest);
const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
try Compilation.renameTmpIntoCache(
comp.dirs.local_cache,
tmp_sub_path,
o_sub_path,
);
man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err});
if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
break :digest .{ bin_digest, hex_digest };
};
if (fancy_output) |p| {
p.digest = bin_digest;
p.errors = std.zig.ErrorBundle.empty;
} else {
const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_zig_basename });
const zig_file = comp.dirs.local_cache.handle.openFile(out_zig_path, .{}) catch |err| {
const path = comp.dirs.local_cache.path orelse ".";
fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{
path,
fs.path.sep_str,
out_zig_path,
@errorName(err),
});
};
defer zig_file.close();
var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
var file_reader = zig_file.reader(&.{});
_ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);
try stdout_writer.interface.flush();
return cleanExit();
}
}
pub fn translateC(
gpa: Allocator,