Commit 8b6cdc3d82

kcbanner <kcbanner@gmail.com>
2025-10-08 09:49:13
- Rework common translate-c and cImport logic into `Compilation.translateC` - Add std.zig.Server.allocErrorBundle, replace duplicates
1 parent 447280d
Changed files (7)
lib/compiler/std-docs.zig
@@ -345,20 +345,7 @@ fn buildWasmBinary(
                 }
             },
             .error_bundle => {
-                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];
-                // TODO: use @ptrCast when the compiler supports it
-                const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
-                const extra_array = try arena.alloc(u32, unaligned_extra.len);
-                @memcpy(extra_array, unaligned_extra);
-                result_error_bundle = .{
-                    .string_bytes = try arena.dupe(u8, string_bytes),
-                    .extra = extra_array,
-                };
+                result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
             },
             .emit_digest => {
                 const EmitDigest = std.zig.Server.Message.EmitDigest;
lib/std/Build/Step.zig
@@ -524,22 +524,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
                 }
             },
             .error_bundle => {
-                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];
-                // TODO: use @ptrCast when the compiler supports it
-                const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
-                {
-                    s.result_error_bundle = .{ .string_bytes = &.{}, .extra = &.{} };
-                    errdefer s.result_error_bundle.deinit(gpa);
-                    s.result_error_bundle.string_bytes = try gpa.dupe(u8, string_bytes);
-                    const extra = try gpa.alloc(u32, unaligned_extra.len);
-                    @memcpy(extra, unaligned_extra);
-                    s.result_error_bundle.extra = extra;
-                }
+                s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
                 // This message indicates the end of the update.
                 if (watch) break :poll;
             },
lib/std/Build/WebServer.zig
@@ -595,19 +595,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
                 }
             },
             .error_bundle => {
-                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: []align(1) const u32 = @ptrCast(extra_bytes);
-                const extra_array = try arena.alloc(u32, unaligned_extra.len);
-                @memcpy(extra_array, unaligned_extra);
-                result_error_bundle = .{
-                    .string_bytes = try arena.dupe(u8, string_bytes),
-                    .extra = extra_array,
-                };
+                result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
             },
             .emit_digest => {
                 const EmitDigest = std.zig.Server.Message.EmitDigest;
lib/std/zig/Server.zig
@@ -231,6 +231,28 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
     try s.out.flush();
 }
 
+pub fn allocErrorBundle(allocator: std.mem.Allocator, body: []const u8) !std.zig.ErrorBundle {
+    const eb_hdr = @as(*align(1) const OutMessage.ErrorBundle, @ptrCast(body));
+    const extra_bytes =
+        body[@sizeOf(OutMessage.ErrorBundle)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
+    const string_bytes =
+        body[@sizeOf(OutMessage.ErrorBundle) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
+    const unaligned_extra: []align(1) const u32 = @ptrCast(extra_bytes);
+
+    var error_bundle: std.zig.ErrorBundle = .{
+        .string_bytes = &.{},
+        .extra = &.{},
+    };
+    errdefer error_bundle.deinit(allocator);
+
+    error_bundle.string_bytes = try allocator.dupe(u8, string_bytes);
+    const extra = try allocator.alloc(u32, unaligned_extra.len);
+    @memcpy(extra, unaligned_extra);
+    error_bundle.extra = extra;
+
+    return error_bundle;
+}
+
 pub const TestMetadata = struct {
     names: []const u32,
     expected_panic_msgs: []const u32,
src/Compilation.zig
@@ -5640,6 +5640,7 @@ pub fn obtainWin32ResourceCacheManifest(comp: *const Compilation) Cache.Manifest
 }
 
 pub const CImportResult = struct {
+    // Only valid if `errors` is not empty
     digest: [Cache.bin_digest_len]u8,
     cache_hit: bool,
     errors: std.zig.ErrorBundle,
@@ -5649,76 +5650,182 @@ pub const CImportResult = struct {
     }
 };
 
-/// Caller owns returned memory.
-pub fn cImport(
+pub fn translateC(
     comp: *Compilation,
-    c_src: []const u8,
+    arena: Allocator,
+    man: *Cache.Manifest,
+    ext: FileExt,
+    source: union(enum) {
+        path: []const u8,
+        c_src: []const u8,
+    },
+    translated_basename: []const u8,
     owner_mod: *Package.Module,
     prog_node: std.Progress.Node,
 ) !CImportResult {
     dev.check(.translate_c_command);
 
-    const cimport_basename = "cimport.h";
-    const translated_basename = "cimport.zig";
+    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_basename });
+    const source_path = switch (source) {
+        .c_src => |c_src| path: {
+            const cimport_basename = "cimport.h";
+            const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;
+            const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path});
+            if (comp.verbose_cimport) log.info("writing C import source to {s}", .{out_h_path});
+            try cache_dir.writeFile(.{ .sub_path = out_h_sub_path, .data = c_src });
+            break :path out_h_path;
+        },
+        .path => |p| p,
+    };
 
-    var man = comp.obtainCObjectCacheManifest(owner_mod);
-    defer man.deinit();
+    const out_dep_path: ?[]const u8 = blk: {
+        if (comp.disable_c_depfile) break :blk null;
+        const c_src_basename = fs.path.basename(source_path);
+        const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});
+        const out_dep_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, dep_basename });
+        break :blk out_dep_path;
+    };
 
-    man.hash.add(@as(u16, 0x7dd9)); // Random number to distinguish translate-c from compiling C objects
-    man.hash.addBytes(c_src);
+    var argv = std.array_list.Managed([]const u8).init(arena);
+    {
+        const target = &owner_mod.resolved_target.result;
+        try argv.appendSlice(&.{ "--zig-integration", "-x", "c" });
 
-    const digest, const is_hit = if (try man.hit()) .{ man.finalBin(), true } else digest: {
-        var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
-        defer arena_allocator.deinit();
-        const arena = arena_allocator.allocator();
+        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);
+        try argv.appendSlice(&[_][]const u8{ "-target", try target.zigTriple(arena) });
 
-        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;
-        const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;
+        const mcpu = mcpu: {
+            var buf: std.ArrayListUnmanaged(u8) = .empty;
+            defer buf.deinit(comp.gpa);
+
+            try buf.print(comp.gpa, "-mcpu={s}", .{target.cpu.model.name});
+
+            // TODO better serialization https://github.com/ziglang/zig/issues/4584
+            const all_features_list = target.cpu.arch.allFeaturesList();
+            try argv.ensureUnusedCapacity(all_features_list.len * 4);
+            for (all_features_list, 0..) |feature, index_usize| {
+                const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
+                const is_enabled = target.cpu.features.isEnabled(index);
 
-        try cache_dir.makePath(tmp_sub_path);
+                const plus_or_minus = "-+"[@intFromBool(is_enabled)];
+                try buf.print(comp.gpa, "{c}{s}", .{ plus_or_minus, feature.name });
+            }
+            break :mcpu try buf.toOwnedSlice(arena);
+        };
+        try argv.append(mcpu);
 
-        const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path});
-        const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename });
-        const out_dep_path = try std.fmt.allocPrint(arena, "{s}.d", .{out_h_path});
+        try argv.appendSlice(comp.global_cc_argv);
+        try argv.appendSlice(owner_mod.cc_argv);
+        try argv.appendSlice(&.{ source_path, "-o", translated_path });
+        if (comp.verbose_cimport) dump_argv(argv.items);
+    }
 
-        if (comp.verbose_cimport) log.info("writing C import source to {s}", .{out_h_path});
-        try cache_dir.writeFile(.{ .sub_path = out_h_sub_path, .data = c_src });
+    var stdout: []u8 = undefined;
+    try @import("main.zig").translateC(comp.gpa, arena, argv.items, prog_node, &stdout);
 
-        var argv = std.array_list.Managed([]const u8).init(comp.gpa);
-        defer argv.deinit();
-        try comp.addTranslateCCArgs(arena, &argv, .c, out_dep_path, owner_mod);
-        try argv.appendSlice(&.{ out_h_path, "-o", translated_path });
+    if (out_dep_path) |dep_file_path| add_deps: {
+        if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path});
 
-        if (comp.verbose_cc) dump_argv(argv.items);
-        var stdout: []u8 = undefined;
-        try @import("main.zig").translateC(comp.gpa, arena, argv.items, prog_node, &stdout);
-        if (comp.verbose_cimport and stdout.len != 0) log.info("unexpected stdout: {s}", .{stdout});
+        const dep_basename = fs.path.basename(dep_file_path);
+        // Add the files depended on to the cache system, if a dep file was emitted
+        man.addDepFilePost(cache_tmp_dir, dep_basename) catch |err| switch (err) {
+            error.FileNotFound => break :add_deps,
+            else => |e| return e,
+        };
 
-        const dep_sub_path = out_h_sub_path ++ ".d";
-        if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_sub_path});
-        try man.addDepFilePost(cache_dir, dep_sub_path);
         switch (comp.cache_use) {
             .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
                 whole.cache_manifest_mutex.lock();
                 defer whole.cache_manifest_mutex.unlock();
-                try whole_cache_manifest.addDepFilePost(cache_dir, dep_sub_path);
+                try whole_cache_manifest.addDepFilePost(cache_tmp_dir, dep_basename);
             },
             .incremental, .none => {},
         }
 
-        const bin_digest = man.finalBin();
-        const hex_digest = Cache.binToHex(bin_digest);
-        const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
+        // Just to save disk space, we delete the file because it is never needed again.
+        cache_tmp_dir.deleteFile(dep_basename) catch |err| {
+            log.warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
+        };
+    }
+
+    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 => {
+                const error_bundle = try std.zig.Server.allocErrorBundle(comp.gpa, body);
+                return .{
+                    .digest = undefined,
+                    .cache_hit = false,
+                    .errors = error_bundle,
+                };
+            },
+            else => unreachable, // No other messagse are sent
+        }
+    }
+
+    const bin_digest = man.finalBin();
+    const hex_digest = Cache.binToHex(bin_digest);
+    const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
 
-        if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path });
-        try renameTmpIntoCache(comp.dirs.local_cache, tmp_sub_path, o_sub_path);
+    if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path });
+    try renameTmpIntoCache(comp.dirs.local_cache, tmp_sub_path, o_sub_path);
 
-        break :digest .{ bin_digest, false };
+    return .{
+        .digest = bin_digest,
+        .cache_hit = false,
+        .errors = ErrorBundle.empty,
     };
+}
 
-    if (man.have_exclusive_lock) {
+/// Caller owns returned memory.
+pub fn cImport(
+    comp: *Compilation,
+    c_src: []const u8,
+    owner_mod: *Package.Module,
+    prog_node: std.Progress.Node,
+) !CImportResult {
+    dev.check(.translate_c_command);
+
+    const translated_basename = "cimport.zig";
+
+    var man = comp.obtainCObjectCacheManifest(owner_mod);
+    defer man.deinit();
+
+    man.hash.add(@as(u16, 0x7dd9)); // Random number to distinguish c-import from compiling C objects
+    man.hash.addBytes(c_src);
+
+    const result: CImportResult = if (try man.hit()) .{
+        .digest = man.finalBin(),
+        .cache_hit = true,
+        .errors = ErrorBundle.empty,
+    } else result: {
+        var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
+        defer arena_allocator.deinit();
+        const arena = arena_allocator.allocator();
+
+        break :result try comp.translateC(
+            arena,
+            &man,
+            .c,
+            .{ .c_src = c_src },
+            translated_basename,
+            owner_mod,
+            prog_node,
+        );
+    };
+
+    if (result.errors.errorMessageCount() == 0 and man.have_exclusive_lock) {
         // Write the updated manifest. This is a no-op if the manifest is not dirty. Note that it is
         // possible we had a hit and the manifest is dirty, for example if the file mtime changed but
         // the contents were the same, we hit the cache but the manifest is dirty and we need to update
@@ -5728,11 +5835,7 @@ pub fn cImport(
         };
     }
 
-    return .{
-        .digest = digest,
-        .cache_hit = is_hit,
-        .errors = std.zig.ErrorBundle.empty,
-    };
+    return result;
 }
 
 fn workerUpdateCObject(
@@ -6622,19 +6725,7 @@ fn spawnZigRc(
             // We expect exactly one ErrorBundle, and if any error_bundle header is
             // sent then it's a fatal error.
             .error_bundle => {
-                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,
-                };
+                const error_bundle = try std.zig.Server.allocErrorBundle(comp.gpa, body);
                 return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle);
             },
             else => {}, // ignore other messages
@@ -6672,49 +6763,6 @@ pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error
     }
 }
 
-pub fn addTranslateCCArgs(
-    comp: *Compilation,
-    arena: Allocator,
-    argv: *std.array_list.Managed([]const u8),
-    ext: FileExt,
-    out_dep_path: ?[]const u8,
-    owner_mod: *Package.Module,
-) !void {
-    const target = &owner_mod.resolved_target.result;
-
-    try argv.appendSlice(&.{ "-x", "c" });
-
-    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);
-
-    try argv.appendSlice(&[_][]const u8{ "-target", try target.zigTriple(arena) });
-
-    const mcpu = mcpu: {
-        var buf: std.ArrayListUnmanaged(u8) = .empty;
-        defer buf.deinit(comp.gpa);
-
-        try buf.print(comp.gpa, "-mcpu={s}", .{target.cpu.model.name});
-
-        // TODO better serialization https://github.com/ziglang/zig/issues/4584
-        const all_features_list = target.cpu.arch.allFeaturesList();
-        try argv.ensureUnusedCapacity(all_features_list.len * 4);
-        for (all_features_list, 0..) |feature, index_usize| {
-            const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
-            const is_enabled = target.cpu.features.isEnabled(index);
-
-            const plus_or_minus = "-+"[@intFromBool(is_enabled)];
-            try buf.print(comp.gpa, "{c}{s}", .{ plus_or_minus, feature.name });
-        }
-        break :mcpu try buf.toOwnedSlice(arena);
-    };
-    try argv.append(mcpu);
-
-    try argv.appendSlice(comp.global_cc_argv);
-    try argv.appendSlice(owner_mod.cc_argv);
-}
-
 /// Add common C compiler args between translate-c and C object compilation.
 fn addCommonCCArgs(
     comp: *const Compilation,
src/main.zig
@@ -4520,12 +4520,11 @@ fn cmdTranslateC(
     prog_node: std.Progress.Node,
 ) !void {
     dev.check(.translate_c_command);
-    const color: Color = .auto;
 
     assert(comp.c_source_files.len == 1);
     const c_source_file = comp.c_source_files[0];
 
-    const translated_zig_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{comp.root_name});
+    const translated_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;
@@ -4537,111 +4536,43 @@ fn cmdTranslateC(
         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.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.appendSlice(&.{ c_source_file.src_path, "-o", translated_path });
-        if (comp.verbose_cc) Compilation.dump_argv(argv.items);
-
-        var stdout: []u8 = undefined;
-        try translateC(comp.gpa, arena, argv.items, prog_node, &stdout);
-
-        if (out_dep_path) |dep_file_path| add_deps: {
-            const dep_basename = fs.path.basename(dep_file_path);
-            // Add the files depended on to the cache system, if a dep file was emitted
-            man.addDepFilePost(cache_tmp_dir, dep_basename) catch |err| switch (err) {
-                error.FileNotFound => break :add_deps,
-                else => |e| return e,
-            };
-            // Just to save disk space, we delete the file because it is never needed again.
-            cache_tmp_dir.deleteFile(dep_basename) catch |err| {
-                warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
-            };
-        }
-
-        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,
-                    };
+    const result: Compilation.CImportResult = if (try man.hit()) .{
+        .digest = man.finalBin(),
+        .cache_hit = true,
+        .errors = std.zig.ErrorBundle.empty,
+    } else result: {
+        const result = try comp.translateC(
+            arena,
+            &man,
+            Compilation.classifyFileExt(c_source_file.src_path),
+            .{ .path = c_source_file.src_path },
+            translated_basename,
+            comp.root_mod,
+            prog_node,
+        );
 
-                    if (fancy_output) |p| {
-                        if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
-                        p.errors = error_bundle;
-                        return;
-                    } else {
-                        error_bundle.renderToStdErr(color.renderOptions());
-                        process.exit(1);
-                    }
-                },
-                else => unreachable, // No other messagse are sent
+        if (result.errors.errorMessageCount() != 0) {
+            if (fancy_output) |p| {
+                if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
+                p.* = result;
+                return;
+            } else {
+                const color: Color = .auto;
+                result.errors.renderToStdErr(color.renderOptions());
+                process.exit(1);
             }
         }
 
-        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 };
+        break :result result;
     };
 
+    if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
     if (fancy_output) |p| {
-        p.digest = bin_digest;
-        p.errors = std.zig.ErrorBundle.empty;
+        p.* = result;
     } else {
-        const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_zig_basename });
+        const hex_digest = Cache.binToHex(result.digest);
+        const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_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}", .{
tools/incr-check.zig
@@ -259,20 +259,7 @@ const Eval = struct {
 
             switch (header.tag) {
                 .error_bundle => {
-                    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];
-                    // TODO: use @ptrCast when the compiler supports it
-                    const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
-                    const extra_array = try arena.alloc(u32, unaligned_extra.len);
-                    @memcpy(extra_array, unaligned_extra);
-                    const result_error_bundle: std.zig.ErrorBundle = .{
-                        .string_bytes = try arena.dupe(u8, string_bytes),
-                        .extra = extra_array,
-                    };
+                    const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
                     if (stderr.bufferedLen() > 0) {
                         const stderr_data = try poller.toOwnedSlice(.stderr);
                         if (eval.allow_stderr) {