From 5b78d471cd30e52e3dda1ac377b502105c7b25a3 Mon Sep 17 00:00:00 2001 From: Xavier Bouchoux Date: Fri, 10 Nov 2023 13:59:56 +0100 Subject: [PATCH] zig cc: expose clang precompiled C header support see https://releases.llvm.org/17.0.1/tools/clang/docs/UsersManual.html#generating-a-pch-file syntax examples: `zig cc -x c-header test.h -o test.pch` `zig cc -include-pch test.pch main.c` `zig c++ -x c++-header test.h -o test.pch` `zig c++ -include-pch test.pch main.cpp` `zig build-obj -lc++ -x c++-header test.h` `zig run -lc++ -cflags -include-pch test.pch -- main.cpp` --- src/Compilation.zig | 34 ++++++++++++++++++++++---------- src/link.zig | 2 +- src/main.zig | 48 +++++++++++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 3bd7d1506cbd..2cb722b3a283 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -218,11 +218,11 @@ pub const LangToExt = std.ComptimeStringMap(FileExt, .{ .{ "c", .c }, .{ "c-header", .h }, .{ "c++", .cpp }, - .{ "c++-header", .h }, + .{ "c++-header", .hpp }, .{ "objective-c", .m }, - .{ "objective-c-header", .h }, + .{ "objective-c-header", .hm }, .{ "objective-c++", .mm }, - .{ "objective-c++-header", .h }, + .{ "objective-c++-header", .hmm }, .{ "assembler", .assembly }, .{ "assembler-with-cpp", .assembly_with_cpp }, .{ "cuda", .cu }, @@ -793,6 +793,8 @@ pub const ClangPreprocessorMode = enum { yes, /// This means we are doing `zig cc -E`. stdout, + /// precompiled C header + pch, }; pub const Framework = link.Framework; @@ -4655,6 +4657,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P .assembly_with_cpp => "assembler-with-cpp", .c => "c", .cpp => "c++", + .h => "c-header", + .hpp => "c++-header", + .hm => "objective-c-header", + .hmm => "objective-c++-header", .cu => "cuda", .m => "objective-c", .mm => "objective-c++", @@ -4680,10 +4686,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P else "/dev/null"; - try argv.ensureUnusedCapacity(5); + try argv.ensureUnusedCapacity(6); switch (comp.clang_preprocessor_mode) { - .no => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-c", "-o", out_obj_path }), - .yes => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-E", "-o", out_obj_path }), + .no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }), + .yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }), + .pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-o", out_obj_path }), .stdout => argv.appendAssumeCapacity("-E"), } @@ -4718,10 +4725,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P try argv.appendSlice(c_object.src.extra_flags); try argv.appendSlice(c_object.src.cache_exempt_flags); - try argv.ensureUnusedCapacity(5); + try argv.ensureUnusedCapacity(6); switch (comp.clang_preprocessor_mode) { .no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }), .yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }), + .pch => argv.appendSliceAssumeCapacity(&.{ "-Xclang", "-emit-pch", "-o", out_obj_path }), .stdout => argv.appendAssumeCapacity("-E"), } if (comp.clang_passthrough_mode) { @@ -5345,7 +5353,7 @@ pub fn addCCArgs( try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple }); if (target.os.tag == .windows) switch (ext) { - .c, .cpp, .m, .mm, .h, .cu, .rc, .assembly, .assembly_with_cpp => { + .c, .cpp, .m, .mm, .h, .hpp, .hm, .hmm, .cu, .rc, .assembly, .assembly_with_cpp => { const minver: u16 = @truncate(@intFromEnum(target.os.getVersionRange().windows.min) >> 16); try argv.append(try std.fmt.allocPrint(argv.allocator, "-D_WIN32_WINNT=0x{x:0>4}", .{minver})); }, @@ -5353,7 +5361,7 @@ pub fn addCCArgs( }; switch (ext) { - .c, .cpp, .m, .mm, .h, .cu, .rc => { + .c, .cpp, .m, .mm, .h, .hpp, .hm, .hmm, .cu, .rc => { try argv.appendSlice(&[_][]const u8{ "-nostdinc", "-fno-spell-checking", @@ -5976,6 +5984,9 @@ pub const FileExt = enum { cpp, cu, h, + hpp, + hm, + hmm, m, mm, ll, @@ -5994,7 +6005,7 @@ pub const FileExt = enum { pub fn clangSupportsDepFile(ext: FileExt) bool { return switch (ext) { - .c, .cpp, .h, .m, .mm, .cu => true, + .c, .cpp, .h, .hpp, .hm, .hmm, .m, .mm, .cu => true, .ll, .bc, @@ -6019,6 +6030,9 @@ pub const FileExt = enum { .cpp => ".cpp", .cu => ".cu", .h => ".h", + .hpp => ".h", + .hm => ".h", + .hmm => ".h", .m => ".m", .mm => ".mm", .ll => ".ll", diff --git a/src/link.zig b/src/link.zig index 1648d6a63efa..d2caf18a1724 100644 --- a/src/link.zig +++ b/src/link.zig @@ -795,7 +795,7 @@ pub const File = struct { assert(base.tag == .c); return @fieldParentPtr(C, "base", base).flush(comp, prog_node); } - if (comp.clang_preprocessor_mode == .yes) { + if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) { const emit = base.options.emit orelse return; // -fno-emit-bin // TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case) // Until then, we do `lld -r -o output.o input.o` even though the output is the same diff --git a/src/main.zig b/src/main.zig index 596d5e985205..126614b925cd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1638,7 +1638,7 @@ fn buildOutputType( fatal("only one manifest file can be specified, found '{s}' after '{s}'", .{ arg, other }); } else manifest_file = arg; }, - .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => { + .assembly, .assembly_with_cpp, .c, .cpp, .h, .hpp, .hm, .hmm, .ll, .bc, .m, .mm, .cu => { try c_source_files.append(.{ .src_path = arg, .extra_flags = try arena.dupe([]const u8, extra_cflags.items), @@ -1669,6 +1669,11 @@ fn buildOutputType( optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, s) orelse fatal("unrecognized optimization mode: '{s}'", .{s}); } + + // precompiled header syntax: "zig build-obj -x c-header test.h" + const emit_pch = ((file_ext == .h or file_ext == .hpp or file_ext == .hm or file_ext == .hmm) and emit_bin != .no); + if (emit_pch) + clang_preprocessor_mode = .pch; }, .cc, .cpp => { if (build_options.only_c) unreachable; @@ -1690,7 +1695,7 @@ fn buildOutputType( assembly, preprocessor, }; - var c_out_mode: COutMode = .link; + var c_out_mode: ?COutMode = null; var out_path: ?[]const u8 = null; var is_shared_lib = false; var linker_args = std.ArrayList([]const u8).init(arena); @@ -1734,7 +1739,7 @@ fn buildOutputType( }, .positional => switch (file_ext orelse Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0))) { - .assembly, .assembly_with_cpp, .c, .cpp, .ll, .bc, .h, .m, .mm, .cu => { + .assembly, .assembly_with_cpp, .c, .cpp, .ll, .bc, .h, .hpp, .hm, .hmm, .m, .mm, .cu => { try c_source_files.append(.{ .src_path = it.only_arg, .ext = file_ext, // duped while parsing the args. @@ -2405,7 +2410,12 @@ fn buildOutputType( } } - switch (c_out_mode) { + // precompiled header syntax: "zig cc -x c-header test.h -o test.pch" + const emit_pch = ((file_ext == .h or file_ext == .hpp or file_ext == .hm or file_ext == .hmm) and c_out_mode == null); + if (emit_pch) + c_out_mode = .preprocessor; + + switch (c_out_mode orelse .link) { .link => { output_mode = if (is_shared_lib) .Lib else .Exe; emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out; @@ -2454,11 +2464,16 @@ fn buildOutputType( // For example `zig cc` and no args should print the "no input files" message. return process.exit(try clangMain(arena, all_args)); } - if (out_path) |p| { - emit_bin = .{ .yes = p }; - clang_preprocessor_mode = .yes; + if (emit_pch) { + emit_bin = if (out_path) |p| .{ .yes = p } else .yes_default_path; + clang_preprocessor_mode = .pch; } else { - clang_preprocessor_mode = .stdout; + if (out_path) |p| { + emit_bin = .{ .yes = p }; + clang_preprocessor_mode = .yes; + } else { + clang_preprocessor_mode = .stdout; + } } }, } @@ -3137,13 +3152,16 @@ fn buildOutputType( }, } }, - .basename = try std.zig.binNameAlloc(arena, .{ - .root_name = root_name, - .target = target_info.target, - .output_mode = output_mode, - .link_mode = link_mode, - .version = optional_version, - }), + .basename = if (clang_preprocessor_mode == .pch) + try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}) + else + try std.zig.binNameAlloc(arena, .{ + .root_name = root_name, + .target = target_info.target, + .output_mode = output_mode, + .link_mode = link_mode, + .version = optional_version, + }), }, .yes => |full_path| b: { const basename = fs.path.basename(full_path);