Commit fe4c348f57

Andrew Kelley <andrew@ziglang.org>
2020-09-27 01:48:02
stage2: `zig translate-c` supports --enable-cache
This matches master branch behavior and makes the test-translate-c tests pass.
1 parent 26f2f9b
src/Compilation.zig
@@ -1251,7 +1251,7 @@ pub fn performAllTheWork(self: *Compilation) error{OutOfMemory}!void {
     };
 }
 
-fn obtainCObjectCacheManifest(comp: *Compilation) Cache.Manifest {
+pub fn obtainCObjectCacheManifest(comp: *Compilation) Cache.Manifest {
     var man = comp.cache_parent.obtain();
 
     // Only things that need to be added on top of the base hash, and only things
@@ -1289,6 +1289,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult {
     var man = comp.obtainCObjectCacheManifest();
     defer man.deinit();
 
+    man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
     man.hash.addBytes(c_src);
 
     // If the previous invocation resulted in clang errors, we will see a hit
@@ -1581,7 +1582,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject) !void {
     };
 }
 
-fn tmpFilePath(comp: *Compilation, arena: *Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
+pub fn tmpFilePath(comp: *Compilation, arena: *Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
     const s = std.fs.path.sep_str;
     const rand_int = comp.rand.int(u64);
     if (comp.local_cache_directory.path) |p| {
@@ -1598,6 +1599,7 @@ pub fn addTranslateCCArgs(
     ext: FileExt,
     out_dep_path: ?[]const u8,
 ) !void {
+    try argv.appendSlice(&[_][]const u8{ "-x", "c" });
     try comp.addCCArgs(arena, argv, ext, out_dep_path);
     // This gives us access to preprocessing entities, presumably at the cost of performance.
     try argv.appendSlice(&[_][]const u8{ "-Xclang", "-detailed-preprocessing-record" });
src/main.zig
@@ -16,6 +16,7 @@ const warn = std.log.warn;
 const introspect = @import("introspect.zig");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 const translate_c = @import("translate_c.zig");
+const Cache = @import("Cache.zig");
 
 pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
     std.log.emerg(format, args);
@@ -481,12 +482,19 @@ fn buildOutputType(
     switch (arg_mode) {
         .build, .translate_c, .zig_test, .run => {
             var optimize_mode_string: ?[]const u8 = null;
-            output_mode = switch (arg_mode) {
-                .build => |m| m,
-                .translate_c => .Obj,
-                .zig_test, .run => .Exe,
+            switch (arg_mode) {
+                .build => |m| {
+                    output_mode = m;
+                },
+                .translate_c => {
+                    emit_bin = .no;
+                    output_mode = .Obj;
+                },
+                .zig_test, .run => {
+                    output_mode = .Exe;
+                },
                 else => unreachable,
-            };
+            }
             // TODO finish self-hosted and add support for emitting C header files
             emit_h = .no;
             //switch (arg_mode) {
@@ -1537,7 +1545,7 @@ fn buildOutputType(
         return std.io.getStdOut().writeAll(try comp.generateBuiltinZigSource(arena));
     }
     if (arg_mode == .translate_c) {
-        return cmdTranslateC(comp, arena);
+        return cmdTranslateC(comp, arena, have_enable_cache);
     }
 
     const hook: AfterUpdateHook = blk: {
@@ -1556,7 +1564,7 @@ fn buildOutputType(
     try updateModule(gpa, comp, zir_out_path, hook);
 
     if (build_options.is_stage1 and comp.stage1_lock != null and watch) {
-        std.log.warn("--watch is not recommended with the stage1 backend; it leaks memory and is not capable of incremental compilation", .{});
+        warn("--watch is not recommended with the stage1 backend; it leaks memory and is not capable of incremental compilation", .{});
     }
 
     switch (arg_mode) {
@@ -1701,61 +1709,121 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8,
     }
 }
 
-fn cmdTranslateC(comp: *Compilation, arena: *Allocator) !void {
+fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void {
     if (!build_options.have_llvm)
         fatal("cannot translate-c: compiler built without LLVM extensions", .{});
 
     assert(comp.c_source_files.len == 1);
+    const c_source_file = comp.c_source_files[0];
 
-    var argv = std.ArrayList([]const u8).init(arena);
+    const translated_zig_basename = try std.fmt.allocPrint(arena, "{}.zig", .{comp.bin_file.options.root_name});
 
-    const c_source_file = comp.c_source_files[0];
-    const file_ext = Compilation.classifyFileExt(c_source_file.src_path);
-    try comp.addTranslateCCArgs(arena, &argv, file_ext, null);
-    try argv.append(c_source_file.src_path);
+    var man: Cache.Manifest = comp.obtainCObjectCacheManifest();
+    defer if (enable_cache) man.deinit();
 
-    if (comp.verbose_cc) {
-        std.debug.print("clang ", .{});
-        Compilation.dump_argv(argv.items);
-    }
+    man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
+    _ = man.addFile(c_source_file.src_path, null) catch |err| {
+        fatal("unable to process '{}': {}", .{ c_source_file.src_path, @errorName(err) });
+    };
 
-    // Convert to null terminated args.
-    const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1);
-    new_argv_with_sentinel[argv.items.len] = null;
-    const new_argv = new_argv_with_sentinel[0..argv.items.len :null];
-    for (argv.items) |arg, i| {
-        new_argv[i] = try arena.dupeZ(u8, arg);
-    }
+    const digest = if (try man.hit()) man.final() else digest: {
+        var argv = std.ArrayList([]const u8).init(arena);
 
-    const c_headers_dir_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{"include"});
-    const c_headers_dir_path_z = try arena.dupeZ(u8, c_headers_dir_path);
-    var clang_errors: []translate_c.ClangErrMsg = &[0]translate_c.ClangErrMsg{};
-    const tree = translate_c.translate(
-        comp.gpa,
-        new_argv.ptr,
-        new_argv.ptr + new_argv.len,
-        &clang_errors,
-        c_headers_dir_path_z,
-    ) catch |err| switch (err) {
-        error.OutOfMemory => return error.OutOfMemory,
-        error.ASTUnitFailure => fatal("clang API returned errors but due to a clang bug, it is not exposing the errors for zig to see. For more details: https://github.com/ziglang/zig/issues/4455", .{}),
-        error.SemanticAnalyzeFail => {
-            for (clang_errors) |clang_err| {
-                std.debug.print("{}:{}:{}: {}\n", .{
-                    if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)",
-                    clang_err.line + 1,
-                    clang_err.column + 1,
-                    clang_err.msg_ptr[0..clang_err.msg_len],
-                });
-            }
-            process.exit(1);
-        },
+        var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{});
+        defer zig_cache_tmp_dir.close();
+
+        const ext = Compilation.classifyFileExt(c_source_file.src_path);
+        const out_dep_path: ?[]const u8 = blk: {
+            if (comp.disable_c_depfile or !ext.clangSupportsDepFile())
+                break :blk null;
+
+            const c_src_basename = fs.path.basename(c_source_file.src_path);
+            const dep_basename = try std.fmt.allocPrint(arena, "{}.d", .{c_src_basename});
+            const out_dep_path = try comp.tmpFilePath(arena, dep_basename);
+            break :blk out_dep_path;
+        };
+
+        try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path);
+        try argv.append(c_source_file.src_path);
+
+        if (comp.verbose_cc) {
+            std.debug.print("clang ", .{});
+            Compilation.dump_argv(argv.items);
+        }
+
+        // Convert to null terminated args.
+        const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1);
+        new_argv_with_sentinel[argv.items.len] = null;
+        const new_argv = new_argv_with_sentinel[0..argv.items.len :null];
+        for (argv.items) |arg, i| {
+            new_argv[i] = try arena.dupeZ(u8, arg);
+        }
+
+        const c_headers_dir_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{"include"});
+        const c_headers_dir_path_z = try arena.dupeZ(u8, c_headers_dir_path);
+        var clang_errors: []translate_c.ClangErrMsg = &[0]translate_c.ClangErrMsg{};
+        const tree = translate_c.translate(
+            comp.gpa,
+            new_argv.ptr,
+            new_argv.ptr + new_argv.len,
+            &clang_errors,
+            c_headers_dir_path_z,
+        ) catch |err| switch (err) {
+            error.OutOfMemory => return error.OutOfMemory,
+            error.ASTUnitFailure => fatal("clang API returned errors but due to a clang bug, it is not exposing the errors for zig to see. For more details: https://github.com/ziglang/zig/issues/4455", .{}),
+            error.SemanticAnalyzeFail => {
+                for (clang_errors) |clang_err| {
+                    std.debug.print("{}:{}:{}: {}\n", .{
+                        if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)",
+                        clang_err.line + 1,
+                        clang_err.column + 1,
+                        clang_err.msg_ptr[0..clang_err.msg_len],
+                    });
+                }
+                process.exit(1);
+            },
+        };
+        defer tree.deinit();
+
+        if (out_dep_path) |dep_file_path| {
+            const dep_basename = std.fs.path.basename(dep_file_path);
+            // Add the files depended on to the cache system.
+            try man.addDepFilePost(zig_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| {
+                warn("failed to delete '{}': {}", .{ dep_file_path, @errorName(err) });
+            };
+        }
+
+        const digest = man.final();
+        const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest });
+        var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{});
+        defer o_dir.close();
+        var zig_file = try o_dir.createFile(translated_zig_basename, .{});
+        defer zig_file.close();
+
+        var bos = io.bufferedOutStream(zig_file.writer());
+        _ = try std.zig.render(comp.gpa, bos.writer(), tree);
+        try bos.flush();
+
+        man.writeManifest() catch |err| warn("failed to write cache manifest: {}", .{@errorName(err)});
+
+        break :digest digest;
     };
-    defer tree.deinit();
 
-    var bos = io.bufferedOutStream(io.getStdOut().writer());
-    _ = try std.zig.render(comp.gpa, bos.writer(), tree);
-    try bos.flush();
+    if (enable_cache) {
+        const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{
+            "o", &digest, translated_zig_basename,
+        });
+        try io.getStdOut().writer().print("{}\n", .{full_zig_path});
+        return cleanExit();
+    } else {
+        const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename });
+        const zig_file = try comp.local_cache_directory.handle.openFile(out_zig_path, .{});
+        defer zig_file.close();
+        try io.getStdOut().writeFileAll(zig_file, .{});
+        return cleanExit();
+    }
 }
 
 pub const usage_libc =
BRANCH_TODO
@@ -1,5 +1,4 @@
  * restore the legacy -femit-h feature using the stage1 backend
- * figure out why test-translate-c is failing
  * tests passing with -Dskip-non-native
  * `-ftime-report`
  *  -fstack-report               print stack size diagnostics\n"