Commit 4aa4d80ec6

kcbanner <kcbanner@gmail.com>
2025-10-07 04:02:14
- 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)
1 parent 328ae41
Changed files (6)
lib
compiler
aro
resinator
translate-c
std
src
lib/compiler/aro/aro/Diagnostics.zig
@@ -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("");
+}
lib/compiler/resinator/main.zig
@@ -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("");
-}
lib/compiler/translate-c/main.zig
@@ -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 {
lib/std/zig/ErrorBundle.zig
@@ -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));
     }
src/Compilation.zig
@@ -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);
src/main.zig
@@ -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,48 +4515,142 @@ 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();
 
-    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);
-        break :blk out_dep_path;
+    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) });
     };
 
-    var argv = std.array_list.Managed([]const u8).init(arena);
-    try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path, comp.root_mod);
-    try argv.append(c_source_file.src_path);
-    if (comp.verbose_cc) Compilation.dump_argv(argv.items);
-
-    try translateC(comp.gpa, arena, argv.items, prog_node, null);
-
-    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,
-        //};
-        // Just to save disk space, we delete the file because it is never needed again.
-        zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| {
-            warn("failed to delete '{s}': {t}", .{ dep_file_path, 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;
         };
-    }
 
-    return cleanExit();
+        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 (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.
+            try man.addDepFilePost(cache_tmp_dir, dep_basename);
+            // 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 });
+            };
+        }
+
+        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(