Commit d11f42c2b2

Andrew Kelley <andrew@ziglang.org>
2021-09-17 01:37:21
zig cc: support -S and -emit-llvm CLI parameters
closes #6425
1 parent 6d37ae9
src/clang_options_data.zig
@@ -2434,7 +2434,14 @@ flagpd1("emit-codegen-only"),
 flagpd1("emit-header-module"),
 flagpd1("emit-html"),
 flagpd1("emit-interface-stubs"),
-flagpd1("emit-llvm"),
+.{
+    .name = "emit-llvm",
+    .syntax = .flag,
+    .zig_equivalent = .emit_llvm,
+    .pd1 = true,
+    .pd2 = false,
+    .psl = false,
+},
 flagpd1("emit-llvm-bc"),
 flagpd1("emit-llvm-only"),
 flagpd1("emit-llvm-uselists"),
src/Compilation.zig
@@ -849,10 +849,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             if (options.use_llvm) |explicit|
                 break :blk explicit;
 
-            // If we have no zig code to compile, no need for LLVM.
-            if (options.main_pkg == null)
-                break :blk false;
-
             // If we are outputting .c code we must use Zig backend.
             if (ofmt == .c)
                 break :blk false;
@@ -861,6 +857,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             if (options.emit_llvm_ir != null or options.emit_llvm_bc != null)
                 break :blk true;
 
+            // If we have no zig code to compile, no need for LLVM.
+            if (options.main_pkg == null)
+                break :blk false;
+
             // The stage1 compiler depends on the stage1 C++ LLVM backend
             // to compile zig code.
             if (use_stage1)
@@ -876,9 +876,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             if (options.use_llvm == true) {
                 return error.ZigCompilerNotBuiltWithLLVMExtensions;
             }
-            if (options.machine_code_model != .default) {
-                return error.MachineCodeModelNotSupportedWithoutLlvm;
-            }
             if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) {
                 return error.EmittingLlvmModuleRequiresUsingLlvmBackend;
             }
@@ -1793,6 +1790,10 @@ pub fn update(self: *Compilation) !void {
         }
     }
 
+    // Flush takes care of -femit-bin, but we still have -femit-llvm-ir, -femit-llvm-bc, and
+    // -femit-asm to handle, in the case of C objects.
+    try self.emitOthers();
+
     // If there are any errors, we anticipate the source files being loaded
     // to report error messages. Otherwise we unload all source files to save memory.
     // The ZIR needs to stay loaded in memory because (1) Decl objects contain references
@@ -1808,6 +1809,37 @@ pub fn update(self: *Compilation) !void {
     }
 }
 
+fn emitOthers(comp: *Compilation) !void {
+    if (comp.bin_file.options.output_mode != .Obj or comp.bin_file.options.module != null or
+        comp.c_object_table.count() == 0)
+    {
+        return;
+    }
+    const obj_path = comp.c_object_table.keys()[0].status.success.object_path;
+    const cwd = std.fs.cwd();
+    const ext = std.fs.path.extension(obj_path);
+    const basename = obj_path[0 .. obj_path.len - ext.len];
+    // This obj path always ends with the object file extension, but if we change the
+    // extension to .ll, .bc, or .s, then it will be the path to those things.
+    const outs = [_]struct {
+        emit: ?EmitLoc,
+        ext: []const u8,
+    }{
+        .{ .emit = comp.emit_asm, .ext = ".s" },
+        .{ .emit = comp.emit_llvm_ir, .ext = ".ll" },
+        .{ .emit = comp.emit_llvm_bc, .ext = ".bc" },
+    };
+    for (outs) |out| {
+        if (out.emit) |loc| {
+            if (loc.directory) |directory| {
+                const src_path = try std.fmt.allocPrint(comp.gpa, "{s}{s}", .{ basename, out.ext });
+                defer comp.gpa.free(src_path);
+                try cwd.copyFile(src_path, directory.handle, loc.basename, .{});
+            }
+        }
+    }
+}
+
 /// Having the file open for writing is problematic as far as executing the
 /// binary is concerned. This will remove the write flag, or close the file,
 /// or whatever is needed so that it can be executed.
@@ -2764,6 +2796,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
     defer man.deinit();
 
     man.hash.add(comp.clang_preprocessor_mode);
+    man.hash.addOptionalEmitLoc(comp.emit_asm);
+    man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
+    man.hash.addOptionalEmitLoc(comp.emit_llvm_bc);
 
     try man.hashCSource(c_object.src);
 
@@ -2787,16 +2822,29 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
         comp.bin_file.options.root_name
     else
         c_source_basename[0 .. c_source_basename.len - std.fs.path.extension(c_source_basename).len];
-    const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{
-        o_basename_noext,
-        comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch),
-    });
 
+    const o_ext = comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch);
     const digest = if (!comp.disable_c_depfile and try man.hit()) man.final() else blk: {
         var argv = std.ArrayList([]const u8).init(comp.gpa);
         defer argv.deinit();
 
-        // We can't know the digest until we do the C compiler invocation, so we need a temporary filename.
+        // In case we are doing passthrough mode, we need to detect -S and -emit-llvm.
+        const out_ext = e: {
+            if (!comp.clang_passthrough_mode)
+                break :e o_ext;
+            if (comp.emit_asm != null)
+                break :e ".s";
+            if (comp.emit_llvm_ir != null)
+                break :e ".ll";
+            if (comp.emit_llvm_bc != null)
+                break :e ".bc";
+
+            break :e o_ext;
+        };
+        const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, out_ext });
+
+        // We can't know the digest until we do the C compiler invocation,
+        // so we need a temporary filename.
         const out_obj_path = try comp.tmpFilePath(arena, o_basename);
         var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{});
         defer zig_cache_tmp_dir.close();
@@ -2810,15 +2858,23 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
             try std.fmt.allocPrint(arena, "{s}.d", .{out_obj_path});
         try comp.addCCArgs(arena, &argv, ext, out_dep_path);
 
-        try argv.ensureCapacity(argv.items.len + 3);
+        try argv.ensureUnusedCapacity(6 + c_object.src.extra_flags.len);
         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 }),
             .stdout => argv.appendAssumeCapacity("-E"),
         }
-
-        try argv.append(c_object.src.src_path);
-        try argv.appendSlice(c_object.src.extra_flags);
+        if (comp.clang_passthrough_mode) {
+            if (comp.emit_asm != null) {
+                argv.appendAssumeCapacity("-S");
+            } else if (comp.emit_llvm_ir != null) {
+                argv.appendSliceAssumeCapacity(&[_][]const u8{ "-emit-llvm", "-S" });
+            } else if (comp.emit_llvm_bc != null) {
+                argv.appendAssumeCapacity("-emit-llvm");
+            }
+        }
+        argv.appendAssumeCapacity(c_object.src.src_path);
+        argv.appendSliceAssumeCapacity(c_object.src.extra_flags);
 
         if (comp.verbose_cc) {
             dump_argv(argv.items);
@@ -2838,8 +2894,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
             switch (term) {
                 .Exited => |code| {
                     if (code != 0) {
-                        // TODO https://github.com/ziglang/zig/issues/6342
-                        std.process.exit(1);
+                        std.process.exit(code);
                     }
                     if (comp.clang_preprocessor_mode == .stdout)
                         std.process.exit(0);
@@ -2855,9 +2910,6 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
 
             const stderr_reader = child.stderr.?.reader();
 
-            // TODO https://github.com/ziglang/zig/issues/6343
-            // Please uncomment and use stdout once this issue is fixed
-            // const stdout = try stdout_reader.readAllAlloc(arena, std.math.maxInt(u32));
             const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
 
             const term = child.wait() catch |err| {
@@ -2907,6 +2959,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
         break :blk digest;
     };
 
+    const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, o_ext });
+
     c_object.status = .{
         .success = .{
             .object_path = try comp.local_cache_directory.join(comp.gpa, &[_][]const u8{
src/main.zig
@@ -1151,6 +1151,7 @@ fn buildOutputType(
             var is_shared_lib = false;
             var linker_args = std.ArrayList([]const u8).init(arena);
             var it = ClangArgIterator.init(arena, all_args);
+            var emit_llvm = false;
             while (it.has_next) {
                 it.next() catch |err| {
                     fatal("unable to parse command line parameters: {s}", .{@errorName(err)});
@@ -1161,6 +1162,7 @@ fn buildOutputType(
                     .c => c_out_mode = .object, // -c
                     .asm_only => c_out_mode = .assembly, // -S
                     .preprocess_only => c_out_mode = .preprocessor, // -E
+                    .emit_llvm => emit_llvm = true,
                     .other => {
                         try clang_argv.appendSlice(it.other_args);
                     },
@@ -1518,22 +1520,42 @@ fn buildOutputType(
                     output_mode = if (is_shared_lib) .Lib else .Exe;
                     emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out;
                     enable_cache = true;
+                    if (emit_llvm) {
+                        fatal("-emit-llvm cannot be used when linking", .{});
+                    }
                 },
                 .object => {
                     output_mode = .Obj;
-                    if (out_path) |p| {
-                        emit_bin = .{ .yes = p };
+                    if (emit_llvm) {
+                        emit_bin = .no;
+                        if (out_path) |p| {
+                            emit_llvm_bc = .{ .yes = p };
+                        } else {
+                            emit_llvm_bc = .yes_default_path;
+                        }
                     } else {
-                        emit_bin = .yes_default_path;
+                        if (out_path) |p| {
+                            emit_bin = .{ .yes = p };
+                        } else {
+                            emit_bin = .yes_default_path;
+                        }
                     }
                 },
                 .assembly => {
                     output_mode = .Obj;
                     emit_bin = .no;
-                    if (out_path) |p| {
-                        emit_asm = .{ .yes = p };
+                    if (emit_llvm) {
+                        if (out_path) |p| {
+                            emit_llvm_ir = .{ .yes = p };
+                        } else {
+                            emit_llvm_ir = .yes_default_path;
+                        }
                     } else {
-                        emit_asm = .yes_default_path;
+                        if (out_path) |p| {
+                            emit_asm = .{ .yes = p };
+                        } else {
+                            emit_asm = .yes_default_path;
+                        }
                     }
                 },
                 .preprocessor => {
@@ -3663,6 +3685,7 @@ pub const ClangArgIterator = struct {
         no_red_zone,
         strip,
         exec_model,
+        emit_llvm,
     };
 
     const Args = struct {
tools/update_clang_options.zig
@@ -376,6 +376,10 @@ const known_options = [_]KnownOpt{
         .name = "mexec-model",
         .ident = "exec_model",
     },
+    .{
+        .name = "emit-llvm",
+        .ident = "emit_llvm",
+    },
 };
 
 const blacklisted_options = [_][]const u8{};