Commit 6af2990549

Andrew Kelley <andrew@ziglang.org>
2020-09-26 10:42:54
implement -femit-asm, -femit-docs, -femit-llvm-ir, etc
These CLI options are now forwarded to the stage1 backend. We're not going to support the -mllvm CLI option any longer. As a compromise, we unconditionally tell LLVM to output intel x86 syntax when using -femit-asm. Simplify stage1 logic; it no longer has the concept of an output directory. --output-dir is no longer a valid CLI option. cmake uses the `-femit-bin=[path]` option. Note the changes to test/cli.zig. This breaks the CLI API that Godbolt is using so we're going to want to open a PR to help them upgrade to the new CLI for the upcoming Zig 0.7.0 release.
1 parent a337046
src/stage1/all_types.hpp
@@ -2116,12 +2116,12 @@ struct CodeGen {
     Buf llvm_triple_str;
     Buf global_asm;
     Buf o_file_output_path;
+    Buf h_file_output_path;
     Buf asm_file_output_path;
     Buf llvm_ir_file_output_path;
+    Buf analysis_json_output_path;
+    Buf docs_output_path;
     Buf *cache_dir;
-    // As an input parameter, mutually exclusive with enable_cache. But it gets
-    // populated in codegen_build_and_link.
-    Buf *output_dir;
     Buf *c_artifact_dir;
     const char **libc_include_dir_list;
     size_t libc_include_dir_len;
@@ -2186,11 +2186,6 @@ struct CodeGen {
     bool dll_export_fns;
     bool have_stack_probing;
     bool function_sections;
-    bool enable_dump_analysis;
-    bool enable_doc_generation;
-    bool emit_bin;
-    bool emit_asm;
-    bool emit_llvm_ir;
     bool test_is_evented;
     bool valgrind_enabled;
 
src/stage1/codegen.cpp
@@ -8243,9 +8243,9 @@ static void zig_llvm_emit_output(CodeGen *g) {
     const char *bin_filename = nullptr;
     const char *llvm_ir_filename = nullptr;
 
-    if (g->emit_bin) bin_filename = buf_ptr(&g->o_file_output_path);
-    if (g->emit_asm) asm_filename = buf_ptr(&g->asm_file_output_path);
-    if (g->emit_llvm_ir) llvm_ir_filename = buf_ptr(&g->llvm_ir_file_output_path);
+    if (buf_len(&g->o_file_output_path) != 0) bin_filename = buf_ptr(&g->o_file_output_path);
+    if (buf_len(&g->asm_file_output_path) != 0) asm_filename = buf_ptr(&g->asm_file_output_path);
+    if (buf_len(&g->llvm_ir_file_output_path) != 0) llvm_ir_filename = buf_ptr(&g->llvm_ir_file_output_path);
 
     // Unfortunately, LLVM shits the bed when we ask for both binary and assembly. So we call the entire
     // pipeline multiple times if this is requested.
@@ -8858,8 +8858,10 @@ static Error define_builtin_compile_vars(CodeGen *g) {
     Buf *contents;
     if (g->builtin_zig_path == nullptr) {
         // Then this is zig0 building stage2. We can make many assumptions about the compilation.
+        Buf *out_dir = buf_alloc();
+        os_path_split(&g->o_file_output_path, out_dir, nullptr);
         g->builtin_zig_path = buf_alloc();
-        os_path_join(g->output_dir, buf_create_from_str(builtin_zig_basename), g->builtin_zig_path);
+        os_path_join(out_dir, buf_create_from_str(builtin_zig_basename), g->builtin_zig_path);
 
         Buf *resolve_paths[] = { g->builtin_zig_path, };
         *g->builtin_zig_path = os_path_resolve(resolve_paths, 1);
@@ -8870,7 +8872,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
             exit(1);
         }
 
-        g->compile_var_package = new_package(buf_ptr(g->output_dir), builtin_zig_basename, "builtin");
+        g->compile_var_package = new_package(buf_ptr(out_dir), builtin_zig_basename, "builtin");
     } else {
         Buf *resolve_paths[] = { g->builtin_zig_path, };
         *g->builtin_zig_path = os_path_resolve(resolve_paths, 1);
@@ -9232,33 +9234,20 @@ void codegen_add_time_event(CodeGen *g, const char *name) {
     g->timing_events.append({seconds, name});
 }
 
-static void resolve_out_paths(CodeGen *g) {
-    assert(g->output_dir != nullptr);
-    assert(g->root_out_name != nullptr);
+void codegen_build_object(CodeGen *g) {
+    g->have_err_ret_tracing = detect_err_ret_tracing(g);
 
-    if (g->emit_bin) {
-        Buf *o_basename = buf_create_from_buf(g->root_out_name);
-        buf_append_str(o_basename, target_o_file_ext(g->zig_target));
-        os_path_join(g->output_dir, o_basename, &g->o_file_output_path);
-    }
-    if (g->emit_asm) {
-        Buf *asm_basename = buf_create_from_buf(g->root_out_name);
-        const char *asm_ext = target_asm_file_ext(g->zig_target);
-        buf_append_str(asm_basename, asm_ext);
-        os_path_join(g->output_dir, asm_basename, &g->asm_file_output_path);
-    }
-    if (g->emit_llvm_ir) {
-        Buf *llvm_ir_basename = buf_create_from_buf(g->root_out_name);
-        const char *llvm_ir_ext = target_llvm_ir_file_ext(g->zig_target);
-        buf_append_str(llvm_ir_basename, llvm_ir_ext);
-        os_path_join(g->output_dir, llvm_ir_basename, &g->llvm_ir_file_output_path);
-    }
-}
+    init(g);
+
+    codegen_add_time_event(g, "Semantic Analysis");
+    const char *progress_name = "Semantic Analysis";
+    codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
+            progress_name, strlen(progress_name), 0));
 
-static void output_type_information(CodeGen *g) {
-    if (g->enable_dump_analysis) {
-        const char *analysis_json_filename = buf_ptr(buf_sprintf("%s" OS_SEP "%s-analysis.json",
-                    buf_ptr(g->output_dir), buf_ptr(g->root_out_name)));
+    gen_root_source(g);
+
+    if (buf_len(&g->analysis_json_output_path) != 0) {
+        const char *analysis_json_filename = buf_ptr(&g->analysis_json_output_path);
         FILE *f = fopen(analysis_json_filename, "wb");
         if (f == nullptr) {
             fprintf(stderr, "Unable to open '%s': %s\n", analysis_json_filename, strerror(errno));
@@ -9270,9 +9259,9 @@ static void output_type_information(CodeGen *g) {
             exit(1);
         }
     }
-    if (g->enable_doc_generation) {
+    if (buf_len(&g->docs_output_path) != 0) {
         Error err;
-        Buf *doc_dir_path = buf_sprintf("%s" OS_SEP "docs", buf_ptr(g->output_dir));
+        Buf *doc_dir_path = &g->docs_output_path;
         if ((err = os_make_path(doc_dir_path))) {
             fprintf(stderr, "Unable to create directory %s: %s\n", buf_ptr(doc_dir_path), err_str(err));
             exit(1);
@@ -9308,27 +9297,6 @@ static void output_type_information(CodeGen *g) {
             exit(1);
         }
     }
-}
-
-void codegen_build_object(CodeGen *g) {
-    assert(g->output_dir != nullptr);
-
-    g->have_err_ret_tracing = detect_err_ret_tracing(g);
-
-    init(g);
-
-    codegen_add_time_event(g, "Semantic Analysis");
-    const char *progress_name = "Semantic Analysis";
-    codegen_switch_sub_prog_node(g, stage2_progress_start(g->main_progress_node,
-            progress_name, strlen(progress_name), 0));
-
-    gen_root_source(g);
-
-    resolve_out_paths(g);
-
-    if (g->enable_dump_analysis || g->enable_doc_generation) {
-        output_type_information(g);
-    }
 
     codegen_add_time_event(g, "Code Generation");
     {
@@ -9379,7 +9347,6 @@ CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget
     bool is_test_build)
 {
     CodeGen *g = heap::c_allocator.create<CodeGen>();
-    g->emit_bin = true;
     g->pass1_arena = heap::ArenaAllocator::construct(&heap::c_allocator, &heap::c_allocator, "pass1");
 
     g->subsystem = TargetSubsystemAuto;
src/stage1/stage1.cpp
@@ -69,7 +69,13 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) {
     CodeGen *g = reinterpret_cast<CodeGen *>(stage1);
 
     g->root_out_name = buf_create_from_mem(stage1->root_name_ptr, stage1->root_name_len);
-    g->output_dir = buf_create_from_mem(stage1->output_dir_ptr, stage1->output_dir_len);
+    buf_init_from_mem(&g->o_file_output_path, stage1->emit_o_ptr, stage1->emit_o_len);
+    buf_init_from_mem(&g->h_file_output_path, stage1->emit_h_ptr, stage1->emit_h_len);
+    buf_init_from_mem(&g->asm_file_output_path, stage1->emit_asm_ptr, stage1->emit_asm_len);
+    buf_init_from_mem(&g->llvm_ir_file_output_path, stage1->emit_llvm_ir_ptr, stage1->emit_llvm_ir_len);
+    buf_init_from_mem(&g->analysis_json_output_path, stage1->emit_analysis_json_ptr, stage1->emit_analysis_json_len);
+    buf_init_from_mem(&g->docs_output_path, stage1->emit_docs_ptr, stage1->emit_docs_len);
+
     if (stage1->builtin_zig_path_len != 0) {
         g->builtin_zig_path = buf_create_from_mem(stage1->builtin_zig_path_ptr, stage1->builtin_zig_path_len);
     }
@@ -94,11 +100,6 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) {
 
     g->enable_time_report = stage1->enable_time_report;
     g->enable_stack_report = stage1->enable_stack_report;
-    g->enable_dump_analysis = stage1->dump_analysis;
-    g->enable_doc_generation = stage1->enable_doc_generation;
-    g->emit_bin = stage1->emit_bin;
-    g->emit_asm = stage1->emit_asm;
-    g->emit_llvm_ir = stage1->emit_llvm_ir;
     g->test_is_evented = stage1->test_is_evented;
 
     g->verbose_tokenize = stage1->verbose_tokenize;
src/stage1/stage1.h
@@ -141,8 +141,23 @@ struct ZigStage1 {
     const char *root_name_ptr;
     size_t root_name_len;
 
-    const char *output_dir_ptr;
-    size_t output_dir_len;
+    const char *emit_o_ptr;
+    size_t emit_o_len;
+
+    const char *emit_h_ptr;
+    size_t emit_h_len;
+
+    const char *emit_asm_ptr;
+    size_t emit_asm_len;
+
+    const char *emit_llvm_ir_ptr;
+    size_t emit_llvm_ir_len;
+
+    const char *emit_analysis_json_ptr;
+    size_t emit_analysis_json_len;
+
+    const char *emit_docs_ptr;
+    size_t emit_docs_len;
 
     const char *builtin_zig_path_ptr;
     size_t builtin_zig_path_len;
@@ -173,11 +188,6 @@ struct ZigStage1 {
     bool enable_stack_probing;
     bool enable_time_report;
     bool enable_stack_report;
-    bool dump_analysis;
-    bool enable_doc_generation;
-    bool emit_bin;
-    bool emit_asm;
-    bool emit_llvm_ir;
     bool test_is_evented;
     bool verbose_tokenize;
     bool verbose_ast;
src/stage1/zig0.cpp
@@ -241,7 +241,7 @@ int main(int argc, char **argv) {
     Error err;
 
     const char *in_file = nullptr;
-    const char *output_dir = nullptr;
+    const char *emit_bin_path = nullptr;
     bool strip = false;
     const char *out_name = nullptr;
     bool verbose_tokenize = false;
@@ -324,14 +324,14 @@ int main(int argc, char **argv) {
                 cur_pkg = cur_pkg->parent;
             } else if (str_starts_with(arg, "-mcpu=")) {
                 mcpu = arg + strlen("-mcpu=");
+            } else if (str_starts_with(arg, "-femit-bin=")) {
+                emit_bin_path = arg + strlen("-femit-bin=");
             } else if (i + 1 >= argc) {
                 fprintf(stderr, "Expected another argument after %s\n", arg);
                 return print_error_usage(arg0);
             } else {
                 i += 1;
-                if (strcmp(arg, "--output-dir") == 0) {
-                    output_dir = argv[i];
-                } else if (strcmp(arg, "--color") == 0) {
+                if (strcmp(arg, "--color") == 0) {
                     if (strcmp(argv[i], "auto") == 0) {
                         color = ErrColorAuto;
                     } else if (strcmp(argv[i], "on") == 0) {
@@ -443,15 +443,14 @@ int main(int argc, char **argv) {
     stage1->verbose_llvm_ir = verbose_llvm_ir;
     stage1->verbose_cimport = verbose_cimport;
     stage1->verbose_llvm_cpu_features = verbose_llvm_cpu_features;
-    stage1->output_dir_ptr = output_dir;
-    stage1->output_dir_len = strlen(output_dir);
+    stage1->emit_o_ptr = emit_bin_path;
+    stage1->emit_o_len = strlen(emit_bin_path);
     stage1->root_pkg = cur_pkg;
     stage1->err_color = color;
     stage1->link_libc = link_libc;
     stage1->link_libcpp = link_libcpp;
     stage1->subsystem = subsystem;
     stage1->pic = true;
-    stage1->emit_bin = true;
 
     zig_stage1_build_object(stage1);
 
src/Compilation.zig
@@ -107,6 +107,12 @@ test_filter: ?[]const u8,
 test_name_prefix: ?[]const u8,
 test_evented_io: bool,
 
+emit_h: ?EmitLoc,
+emit_asm: ?EmitLoc,
+emit_llvm_ir: ?EmitLoc,
+emit_analysis: ?EmitLoc,
+emit_docs: ?EmitLoc,
+
 pub const InnerError = Module.InnerError;
 
 pub const CRTFile = struct {
@@ -296,6 +302,12 @@ pub const InitOptions = struct {
     emit_h: ?EmitLoc = null,
     /// `null` means to not emit assembly.
     emit_asm: ?EmitLoc = null,
+    /// `null` means to not emit LLVM IR.
+    emit_llvm_ir: ?EmitLoc = null,
+    /// `null` means to not emit semantic analysis JSON.
+    emit_analysis: ?EmitLoc = null,
+    /// `null` means to not emit docs.
+    emit_docs: ?EmitLoc = null,
     link_mode: ?std.builtin.LinkMode = null,
     dll_export_fns: ?bool = false,
     /// Normally when using LLD to link, Zig uses a file named "lld.id" in the
@@ -442,7 +454,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             break :blk false;
         };
 
-
         const link_libc = options.link_libc or
             (is_exe_or_dyn_lib and target_util.osRequiresLibC(options.target));
 
@@ -489,9 +500,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             break :pic explicit;
         } else must_pic;
 
-        if (options.emit_h != null) fatal("-femit-h not supported yet", .{}); // TODO
-        if (options.emit_asm != null) fatal("-femit-asm not supported yet", .{}); // TODO
-
         const emit_bin = options.emit_bin orelse fatal("-fno-emit-bin not supported yet", .{}); // TODO
 
         // Make a decision on whether to use Clang for translate-c and compiling C files.
@@ -579,6 +587,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
         cache.hash.add(options.link_libcpp);
         cache.hash.add(options.output_mode);
         cache.hash.add(options.machine_code_model);
+        cache.hash.add(options.emit_bin != null);
+        cache.hash.add(options.emit_h != null);
+        cache.hash.add(options.emit_asm != null);
+        cache.hash.add(options.emit_llvm_ir != null);
+        cache.hash.add(options.emit_analysis != null);
+        cache.hash.add(options.emit_docs != null);
         // TODO audit this and make sure everything is in it
 
         const module: ?*Module = if (options.root_pkg) |root_pkg| blk: {
@@ -752,6 +766,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .local_cache_directory = options.local_cache_directory,
             .global_cache_directory = options.global_cache_directory,
             .bin_file = bin_file,
+            .emit_h = options.emit_h,
+            .emit_asm = options.emit_asm,
+            .emit_llvm_ir = options.emit_llvm_ir,
+            .emit_analysis = options.emit_analysis,
+            .emit_docs = options.emit_docs,
             .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
             .keep_source_files_loaded = options.keep_source_files_loaded,
             .use_clang = use_clang,
@@ -1450,8 +1469,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject) !void {
 
         try argv.ensureCapacity(argv.items.len + 3);
         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(&[_][]const u8{ "-c", "-o", out_obj_path }),
+            .yes => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-E", "-o", out_obj_path }),
             .stdout => argv.appendAssumeCapacity("-E"),
         }
 
@@ -2498,15 +2517,35 @@ fn updateStage1Module(comp: *Compilation) !void {
         comp.is_test,
     ) orelse return error.OutOfMemory;
 
+    const bin_basename = try std.zig.binNameAlloc(arena, .{
+        .root_name = comp.bin_file.options.root_name,
+        .target = target,
+        .output_mode = .Obj,
+    });
+    const emit_bin_path = try directory.join(arena, &[_][]const u8{bin_basename});
+    const emit_h_path = try stage1LocPath(arena, comp.emit_h, directory);
+    const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
+    const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
+    const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
+    const emit_docs_path = try stage1LocPath(arena, comp.emit_docs, directory);
     const stage1_pkg = try createStage1Pkg(arena, "root", mod.root_pkg, null);
-    const output_dir = directory.path orelse ".";
     const test_filter = comp.test_filter orelse ""[0..0];
     const test_name_prefix = comp.test_name_prefix orelse ""[0..0];
     stage1_module.* = .{
         .root_name_ptr = comp.bin_file.options.root_name.ptr,
         .root_name_len = comp.bin_file.options.root_name.len,
-        .output_dir_ptr = output_dir.ptr,
-        .output_dir_len = output_dir.len,
+        .emit_o_ptr = emit_bin_path.ptr,
+        .emit_o_len = emit_bin_path.len,
+        .emit_h_ptr = emit_h_path.ptr,
+        .emit_h_len = emit_h_path.len,
+        .emit_asm_ptr = emit_asm_path.ptr,
+        .emit_asm_len = emit_asm_path.len,
+        .emit_llvm_ir_ptr = emit_llvm_ir_path.ptr,
+        .emit_llvm_ir_len = emit_llvm_ir_path.len,
+        .emit_analysis_json_ptr = emit_analysis_path.ptr,
+        .emit_analysis_json_len = emit_analysis_path.len,
+        .emit_docs_ptr = emit_docs_path.ptr,
+        .emit_docs_len = emit_docs_path.len,
         .builtin_zig_path_ptr = builtin_zig_path.ptr,
         .builtin_zig_path_len = builtin_zig_path.len,
         .test_filter_ptr = test_filter.ptr,
@@ -2530,11 +2569,6 @@ fn updateStage1Module(comp: *Compilation) !void {
         .enable_stack_probing = comp.bin_file.options.stack_check,
         .enable_time_report = comp.time_report,
         .enable_stack_report = false,
-        .dump_analysis = false,
-        .enable_doc_generation = false,
-        .emit_bin = true,
-        .emit_asm = false,
-        .emit_llvm_ir = false,
         .test_is_evented = comp.test_evented_io,
         .verbose_tokenize = comp.verbose_tokenize,
         .verbose_ast = comp.verbose_ast,
@@ -2565,6 +2599,12 @@ fn updateStage1Module(comp: *Compilation) !void {
     comp.stage1_lock = man.toOwnedLock();
 }
 
+fn stage1LocPath(arena: *Allocator, opt_loc: ?EmitLoc, cache_directory: Directory) ![]const u8 {
+    const loc = opt_loc orelse return "";
+    const directory = loc.directory orelse cache_directory;
+    return directory.join(arena, &[_][]const u8{loc.basename});
+}
+
 fn createStage1Pkg(
     arena: *Allocator,
     name: []const u8,
src/llvm.zig
@@ -72,3 +72,6 @@ pub const OSType = extern enum(c_int) {
     WASI = 34,
     Emscripten = 35,
 };
+
+pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions;
+extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void;
src/main.zig
@@ -195,8 +195,20 @@ const usage_build_generic =
     \\  -h, --help                Print this help and exit
     \\  --watch                   Enable compiler REPL
     \\  --color [auto|off|on]     Enable or disable colored error messages
-    \\  -femit-bin[=path]         (default) output machine code
+    \\  -femit-bin[=path]         (default) Output machine code
     \\  -fno-emit-bin             Do not output machine code
+    \\  -femit-asm[=path]         Output .s (assembly code)
+    \\  -fno-emit-asm             (default) Do not output .s (assembly code)
+    \\  -femit-zir[=path]         Produce a .zir file with Zig IR
+    \\  -fno-emit-zir             (default) Do not produce a .zir file with Zig IR
+    \\  -femit-llvm-ir[=path]     Produce a .ll file with LLVM IR (requires LLVM extensions)
+    \\  -fno-emit-llvm-ir         (default) Do not produce a .ll file with LLVM IR
+    \\  -femit-h[=path]           Generate a C header file (.h)
+    \\  -fno-emit-h               (default) Do not generate a C header file (.h)
+    \\  -femit-docs[=path]        Create a docs/ dir with html documentation
+    \\  -fno-emit-docs            (default) Do not produce docs/ dir with html documentation
+    \\  -femit-analysis[=path]    Write analysis JSON file with type information
+    \\  -fno-emit-analysis        (default) Do not write analysis JSON file with type information
     \\  --show-builtin            Output the source of @import("builtin") then exit
     \\  --cache-dir [path]        Override the local cache directory
     \\  --global-cache-dir [path] Override the global cache directory
@@ -210,10 +222,11 @@ const usage_build_generic =
     \\            small|kernel|
     \\            medium|large]
     \\  --name [name]             Override root name (not a file path)
-    \\  -ODebug                   (default) optimizations off, safety on
-    \\  -OReleaseFast             Optimizations on, safety off
-    \\  -OReleaseSafe             Optimizations on, safety on
-    \\  -OReleaseSmall            Optimize for small binary, safety off
+    \\  -O [mode]                 Choose what to optimize for
+    \\    Debug                   (default) Optimizations off, safety on
+    \\    ReleaseFast             Optimizations on, safety off
+    \\    ReleaseSafe             Optimizations on, safety on
+    \\    ReleaseSmall            Optimize for small binary, safety off
     \\  --pkg-begin [name] [path] Make pkg available to import and push current pkg
     \\  --pkg-end                 Pop current pkg
     \\  --main-pkg-path           Set the directory of the root package
@@ -290,9 +303,57 @@ const Emit = union(enum) {
     no,
     yes_default_path,
     yes: []const u8,
+
+    const Resolved = struct {
+        data: ?Compilation.EmitLoc,
+        dir: ?fs.Dir,
+
+        fn deinit(self: *Resolved) void {
+            if (self.dir) |*dir| {
+                dir.close();
+            }
+        }
+    };
+
+    fn resolve(emit: Emit, default_basename: []const u8) !Resolved {
+        var resolved: Resolved = .{ .data = null, .dir = null };
+        errdefer resolved.deinit();
+
+        switch (emit) {
+            .no => {},
+            .yes_default_path => {
+                resolved.data = Compilation.EmitLoc{
+                    .directory = .{ .path = null, .handle = fs.cwd() },
+                    .basename = default_basename,
+                };
+            },
+            .yes => |full_path| {
+                const basename = fs.path.basename(full_path);
+                if (fs.path.dirname(full_path)) |dirname| {
+                    const handle = try fs.cwd().openDir(dirname, .{});
+                    resolved = .{
+                        .dir = handle,
+                        .data = Compilation.EmitLoc{
+                            .basename = basename,
+                            .directory = .{
+                                .path = dirname,
+                                .handle = handle,
+                            },
+                        },
+                    };
+                } else {
+                    resolved.data = Compilation.EmitLoc{
+                        .basename = basename,
+                        .directory = .{ .path = null, .handle = fs.cwd() },
+                    };
+                }
+            },
+        }
+        return resolved;
+    }
 };
 
-pub fn buildOutputType(
+fn buildOutputType(
     gpa: *Allocator,
     arena: *Allocator,
     all_args: []const []const u8,
@@ -328,7 +389,10 @@ pub fn buildOutputType(
     var show_builtin = false;
     var emit_bin: Emit = .yes_default_path;
     var emit_asm: Emit = .no;
+    var emit_llvm_ir: Emit = .no;
     var emit_zir: Emit = .no;
+    var emit_docs: Emit = .no;
+    var emit_analysis: Emit = .no;
     var target_arch_os_abi: []const u8 = "native";
     var target_mcpu: ?[]const u8 = null;
     var target_dynamic_linker: ?[]const u8 = null;
@@ -667,6 +731,30 @@ pub fn buildOutputType(
                         emit_h = .{ .yes = arg["-femit-h=".len..] };
                     } else if (mem.eql(u8, arg, "-fno-emit-h")) {
                         emit_h = .no;
+                    } else if (mem.eql(u8, arg, "-femit-asm")) {
+                        emit_asm = .yes_default_path;
+                    } else if (mem.startsWith(u8, arg, "-femit-asm=")) {
+                        emit_asm = .{ .yes = arg["-femit-asm=".len..] };
+                    } else if (mem.eql(u8, arg, "-fno-emit-asm")) {
+                        emit_asm = .no;
+                    } else if (mem.eql(u8, arg, "-femit-llvm-ir")) {
+                        emit_llvm_ir = .yes_default_path;
+                    } else if (mem.startsWith(u8, arg, "-femit-llvm-ir=")) {
+                        emit_llvm_ir = .{ .yes = arg["-femit-llvm-ir=".len..] };
+                    } else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) {
+                        emit_llvm_ir = .no;
+                    } else if (mem.eql(u8, arg, "-femit-docs")) {
+                        emit_docs = .yes_default_path;
+                    } else if (mem.startsWith(u8, arg, "-femit-docs=")) {
+                        emit_docs = .{ .yes = arg["-femit-docs=".len..] };
+                    } else if (mem.eql(u8, arg, "-fno-emit-docs")) {
+                        emit_docs = .no;
+                    } else if (mem.eql(u8, arg, "-femit-analysis")) {
+                        emit_analysis = .yes_default_path;
+                    } else if (mem.startsWith(u8, arg, "-femit-analysis=")) {
+                        emit_analysis = .{ .yes = arg["-femit-analysis=".len..] };
+                    } else if (mem.eql(u8, arg, "-fno-emit-analysis")) {
+                        emit_analysis = .no;
                     } else if (mem.eql(u8, arg, "-dynamic")) {
                         link_mode = .Dynamic;
                     } else if (mem.eql(u8, arg, "-static")) {
@@ -1237,35 +1325,24 @@ pub fn buildOutputType(
         },
     };
 
-    var cleanup_emit_h_dir: ?fs.Dir = null;
-    defer if (cleanup_emit_h_dir) |*dir| dir.close();
+    const default_h_basename = try std.fmt.allocPrint(arena, "{}.h", .{root_name});
+    var emit_h_resolved = try emit_h.resolve(default_h_basename);
+    defer emit_h_resolved.deinit();
 
-    const emit_h_loc: ?Compilation.EmitLoc = switch (emit_h) {
-        .no => null,
-        .yes_default_path => Compilation.EmitLoc{
-            .directory = .{ .path = null, .handle = fs.cwd() },
-            .basename = try std.fmt.allocPrint(arena, "{}.h", .{root_name}),
-        },
-        .yes => |full_path| b: {
-            const basename = fs.path.basename(full_path);
-            if (fs.path.dirname(full_path)) |dirname| {
-                const handle = try fs.cwd().openDir(dirname, .{});
-                cleanup_emit_h_dir = handle;
-                break :b Compilation.EmitLoc{
-                    .basename = basename,
-                    .directory = .{
-                        .path = dirname,
-                        .handle = handle,
-                    },
-                };
-            } else {
-                break :b Compilation.EmitLoc{
-                    .basename = basename,
-                    .directory = .{ .path = null, .handle = fs.cwd() },
-                };
-            }
-        },
-    };
+    const default_asm_basename = try std.fmt.allocPrint(arena, "{}.s", .{root_name});
+    var emit_asm_resolved = try emit_asm.resolve(default_asm_basename);
+    defer emit_asm_resolved.deinit();
+
+    const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{}.ll", .{root_name});
+    var emit_llvm_ir_resolved = try emit_llvm_ir.resolve(default_llvm_ir_basename);
+    defer emit_llvm_ir_resolved.deinit();
+
+    const default_analysis_basename = try std.fmt.allocPrint(arena, "{}-analysis.json", .{root_name});
+    var emit_analysis_resolved = try emit_analysis.resolve(default_analysis_basename);
+    defer emit_analysis_resolved.deinit();
+
+    var emit_docs_resolved = try emit_docs.resolve("docs");
+    defer emit_docs_resolved.deinit();
 
     const zir_out_path: ?[]const u8 = switch (emit_zir) {
         .no => null,
@@ -1365,6 +1442,12 @@ pub fn buildOutputType(
         };
     };
 
+    if (build_options.have_llvm and emit_asm != .no) {
+        // LLVM has no way to set this non-globally.
+        const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" };
+        @import("llvm.zig").ParseCommandLineOptions(argv.len, &argv);
+    }
+
     gimmeMoreOfThoseSweetSweetFileDescriptors();
 
     const comp = Compilation.create(gpa, .{
@@ -1378,7 +1461,11 @@ pub fn buildOutputType(
         .output_mode = output_mode,
         .root_pkg = root_pkg,
         .emit_bin = emit_bin_loc,
-        .emit_h = emit_h_loc,
+        .emit_h = emit_h_resolved.data,
+        .emit_asm = emit_asm_resolved.data,
+        .emit_llvm_ir = emit_llvm_ir_resolved.data,
+        .emit_docs = emit_docs_resolved.data,
+        .emit_analysis = emit_analysis_resolved.data,
         .link_mode = link_mode,
         .dll_export_fns = dll_export_fns,
         .object_format = object_format,
src/stage1.zig
@@ -75,8 +75,18 @@ pub const Pkg = extern struct {
 pub const Module = extern struct {
     root_name_ptr: [*]const u8,
     root_name_len: usize,
-    output_dir_ptr: [*]const u8,
-    output_dir_len: usize,
+    emit_o_ptr: [*]const u8,
+    emit_o_len: usize,
+    emit_h_ptr: [*]const u8,
+    emit_h_len: usize,
+    emit_asm_ptr: [*]const u8,
+    emit_asm_len: usize,
+    emit_llvm_ir_ptr: [*]const u8,
+    emit_llvm_ir_len: usize,
+    emit_analysis_json_ptr: [*]const u8,
+    emit_analysis_json_len: usize,
+    emit_docs_ptr: [*]const u8,
+    emit_docs_len: usize,
     builtin_zig_path_ptr: [*]const u8,
     builtin_zig_path_len: usize,
     test_filter_ptr: [*]const u8,
@@ -101,11 +111,6 @@ pub const Module = extern struct {
     enable_stack_probing: bool,
     enable_time_report: bool,
     enable_stack_report: bool,
-    dump_analysis: bool,
-    enable_doc_generation: bool,
-    emit_bin: bool,
-    emit_asm: bool,
-    emit_llvm_ir: bool,
     test_is_evented: bool,
     verbose_tokenize: bool,
     verbose_ast: bool,
test/cli.zig
@@ -118,17 +118,20 @@ fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void {
         \\}
     );
 
-    const args = [_][]const u8{
+    var args = std.ArrayList([]const u8).init(a);
+    try args.appendSlice(&[_][]const u8{
         zig_exe,          "build-obj",
         "--cache-dir",    dir_path,
         "--name",         "example",
-        "--output-dir",   dir_path,
-        "--emit",         "asm",
-        "-mllvm",         "--x86-asm-syntax=intel",
-        "--strip",        "--release-fast",
-        example_zig_path, "--disable-gen-h",
-    };
-    _ = try exec(dir_path, &args);
+        "-fno-emit-bin",  "-fno-emit-h",
+        "--strip",        "-OReleaseFast",
+        example_zig_path,
+    });
+
+    const emit_asm_arg = try std.fmt.allocPrint(a, "-femit-asm={s}", .{example_s_path});
+    try args.append(emit_asm_arg);
+
+    _ = try exec(dir_path, args.items);
 
     const out_asm = try std.fs.cwd().readFileAlloc(a, example_s_path, std.math.maxInt(usize));
     testing.expect(std.mem.indexOf(u8, out_asm, "square:") != null);
BRANCH_TODO
@@ -1,13 +1,7 @@
+ * support -fno-emit-bin for godbolt
  * tests passing with -Dskip-non-native
  * `-ftime-report`
  *  -fstack-report               print stack size diagnostics\n"
- *  -fdump-analysis              write analysis.json file with type information\n"
- *  -femit-docs                  create a docs/ dir with html documentation\n"
- *  -fno-emit-docs               do not produce docs/ dir with html documentation\n"
- *  -femit-asm                   output .s (assembly code)\n"
- *  -fno-emit-asm                (default) do not output .s (assembly code)\n"
- *  -femit-llvm-ir               produce a .ll file with LLVM IR\n"
- *  -fno-emit-llvm-ir            (default) do not produce a .ll file with LLVM IR\n"
  * mingw-w64
  * MachO LLD linking
  * COFF LLD linking
@@ -17,8 +11,8 @@
  * On operating systems that support it, do an execve for `zig test` and `zig run` rather than child process.
  * restore error messages for stage2_add_link_lib
  * windows CUSTOMBUILD : error : unable to build compiler_rt: FileNotFound [D:\a\1\s\build\zig_install_lib_files.vcxproj]
- * try building some software with zig cc
- * implement support for -femit-asm
+ * try building some software with zig cc to make sure it didn't regress
+ * restore the legacy -femit-h feature using the stage1 backend
 
  * implement proper parsing of clang stderr/stdout and exposing compile errors with the Compilation API
  * implement proper parsing of LLD stderr/stdout and exposing compile errors with the Compilation API
@@ -56,3 +50,4 @@
  * make std.Progress support multithreaded
  * update musl.zig static data to use native path separator in static data rather than replacing '/' at runtime
  * linking hello world with LLD, lld is silently calling exit(1) instead of reporting ok=false. when run standalone the error message is: ld.lld: error: section [index 3] has a sh_offset (0x57000) + sh_size (0x68) that is greater than the file size (0x57060)
+ * submit PR to godbolt and update the CLI options (see changes to test/cli.zig)
CMakeLists.txt
@@ -449,7 +449,7 @@ endif()
 if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
     set(ZIG1_RELEASE_ARG "")
 else()
-    set(ZIG1_RELEASE_ARG --release-fast --strip)
+    set(ZIG1_RELEASE_ARG -OReleaseFast --strip)
 endif()
 
 set(BUILD_ZIG1_ARGS
@@ -458,8 +458,8 @@ set(BUILD_ZIG1_ARGS
     "-mcpu=${ZIG_TARGET_MCPU}"
     --name zig1
     --override-lib-dir "${CMAKE_SOURCE_DIR}/lib"
-    --output-dir "${CMAKE_BINARY_DIR}"
-    ${ZIG1_RELEASE_ARG}
+    "-femit-bin=${ZIG1_OBJECT}"
+    "${ZIG1_RELEASE_ARG}"
     -lc
     --pkg-begin build_options "${ZIG_CONFIG_ZIG_OUT}"
     --pkg-end