Commit 503ba7b27c

Andrew Kelley <andrew@ziglang.org>
2020-09-04 05:23:00
start moving `zig cc` to stage2
* build.zig: repair the ability to link against llvm, clang, and lld * move the zig cc arg parsing logic to stage2 - the preprocessor flag is still TODO - the clang arg iterator code is improved to use slices instead of raw pointers because it no longer has to deal with an extern struct. * clean up error printing with a `fatal` function and use log API for messages rather than std.debug.print * add support for more CLI options to stage2 & update usage text - hooking up most of these new options is TODO * clean up the way libc and libc++ are detected via command line options. target information is used to determine if any of the libc candidate names are chosen. * add native library directory detection * implement the ability to invoke clang from stage2 * introduce a build_options.have_llvm so we can comptime branch on whether LLVM is linked in or not.
1 parent 749417a
src/config.zig.in
@@ -1,3 +1,5 @@
+pub const have_llvm = true;
 pub const version: []const u8 = "@ZIG_VERSION@";
 pub const log_scopes: []const []const u8 = &[_][]const u8{};
+pub const zir_dumps: []const []const u8 = &[_][]const u8{};
 pub const enable_tracy = false;
src/main.cpp
@@ -404,7 +404,6 @@ static int main0(int argc, char **argv) {
     ZigList<const char *> framework_dirs = {0};
     ZigList<const char *> frameworks = {0};
     bool have_libc = false;
-    bool have_libcpp = false;
     const char *target_string = nullptr;
     bool rdynamic = false;
     const char *linker_script = nullptr;
@@ -446,18 +445,8 @@ static int main0(int argc, char **argv) {
     bool function_sections = false;
     const char *mcpu = nullptr;
     CodeModel code_model = CodeModelDefault;
-    const char *override_soname = nullptr;
-    bool only_pp_or_asm = false;
-    bool ensure_libc_on_non_freestanding = false;
-    bool ensure_libcpp_on_non_freestanding = false;
-    bool disable_c_depfile = false;
     bool want_native_include_dirs = false;
-    Buf *linker_optimization = nullptr;
-    OptionalBool linker_gc_sections = OptionalBoolNull;
-    OptionalBool linker_allow_shlib_undefined = OptionalBoolNull;
     OptionalBool linker_bind_global_refs_locally = OptionalBoolNull;
-    bool linker_z_nodelete = false;
-    bool linker_z_defs = false;
     size_t stack_size_override = 0;
 
     ZigList<const char *> llvm_argv = {0};
@@ -585,355 +574,10 @@ static int main0(int argc, char **argv) {
         return stage2_fmt(argc, argv);
     } else if (argc >= 2 && strcmp(argv[1], "env") == 0) {
         return stage2_env(argc, argv);
-    } else if (argc >= 2 && (strcmp(argv[1], "cc") == 0 || strcmp(argv[1], "c++") == 0)) {
-        emit_h = false;
-        strip = true;
-        ensure_libc_on_non_freestanding = true;
-        ensure_libcpp_on_non_freestanding = (strcmp(argv[1], "c++") == 0);
-        want_native_include_dirs = true;
-
-        bool c_arg = false;
-        Stage2ClangArgIterator it;
-        stage2_clang_arg_iterator(&it, argc, argv);
-        bool is_shared_lib = false;
-        ZigList<Buf *> linker_args = {};
-        while (it.has_next) {
-            if ((err = stage2_clang_arg_next(&it))) {
-                fprintf(stderr, "unable to parse command line parameters: %s\n", err_str(err));
-                return EXIT_FAILURE;
-            }
-            switch (it.kind) {
-                case Stage2ClangArgTarget: // example: -target riscv64-linux-unknown
-                    target_string = it.only_arg;
-                    break;
-                case Stage2ClangArgO: // -o
-                    emit_bin_override_path = it.only_arg;
-                    enable_cache = CacheOptOn;
-                    break;
-                case Stage2ClangArgC: // -c
-                    c_arg = true;
-                    break;
-                case Stage2ClangArgOther:
-                    for (size_t i = 0; i < it.other_args_len; i += 1) {
-                        clang_argv.append(it.other_args_ptr[i]);
-                    }
-                    break;
-                case Stage2ClangArgPositional: {
-                    FileExt file_ext = classify_file_ext(it.only_arg, strlen(it.only_arg));
-                    switch (file_ext) {
-                        case FileExtAsm:
-                        case FileExtC:
-                        case FileExtCpp:
-                        case FileExtLLVMIr:
-                        case FileExtLLVMBitCode:
-                        case FileExtHeader: {
-                            CFile *c_file = heap::c_allocator.create<CFile>();
-                            c_file->source_path = it.only_arg;
-                            c_source_files.append(c_file);
-                            break;
-                        }
-                        case FileExtUnknown:
-                            objects.append(it.only_arg);
-                            break;
-                    }
-                    break;
-                }
-                case Stage2ClangArgL: // -l
-                    if (strcmp(it.only_arg, "c") == 0) {
-                        have_libc = true;
-                        link_libs.append("c");
-                    } else if (strcmp(it.only_arg, "c++") == 0 ||
-                        strcmp(it.only_arg, "stdc++") == 0)
-                    {
-                        have_libcpp = true;
-                        link_libs.append("c++");
-                    } else {
-                        link_libs.append(it.only_arg);
-                    }
-                    break;
-                case Stage2ClangArgIgnore:
-                    break;
-                case Stage2ClangArgDriverPunt:
-                    // Never mind what we're doing, just pass the args directly. For example --help.
-                    return ZigClang_main(argc, argv);
-                case Stage2ClangArgPIC:
-                    want_pic = WantPICEnabled;
-                    break;
-                case Stage2ClangArgNoPIC:
-                    want_pic = WantPICDisabled;
-                    break;
-                case Stage2ClangArgNoStdLib:
-                    ensure_libc_on_non_freestanding = false;
-                    break;
-                case Stage2ClangArgNoStdLibCpp:
-                    ensure_libcpp_on_non_freestanding = false;
-                    break;
-                case Stage2ClangArgShared:
-                    is_dynamic = true;
-                    is_shared_lib = true;
-                    break;
-                case Stage2ClangArgRDynamic:
-                    rdynamic = true;
-                    break;
-                case Stage2ClangArgWL: {
-                    const char *arg = it.only_arg;
-                    for (;;) {
-                        size_t pos = 0;
-                        while (arg[pos] != ',' && arg[pos] != 0) pos += 1;
-                        linker_args.append(buf_create_from_mem(arg, pos));
-                        if (arg[pos] == 0) break;
-                        arg += pos + 1;
-                    }
-                    break;
-                }
-                case Stage2ClangArgPreprocessOrAsm:
-                    // this handles both -E and -S
-                    only_pp_or_asm = true;
-                    for (size_t i = 0; i < it.other_args_len; i += 1) {
-                        clang_argv.append(it.other_args_ptr[i]);
-                    }
-                    break;
-                case Stage2ClangArgOptimize:
-                    // alright what release mode do they want?
-                    if (strcmp(it.only_arg, "Os") == 0) {
-                        build_mode = BuildModeSmallRelease;
-                    } else if (strcmp(it.only_arg, "O2") == 0 ||
-                            strcmp(it.only_arg, "O3") == 0 ||
-                            strcmp(it.only_arg, "O4") == 0)
-                    {
-                        build_mode = BuildModeFastRelease;
-                    } else if (strcmp(it.only_arg, "Og") == 0 ||
-                            strcmp(it.only_arg, "O0") == 0)
-                    {
-                        build_mode = BuildModeDebug;
-                    } else {
-                        for (size_t i = 0; i < it.other_args_len; i += 1) {
-                            clang_argv.append(it.other_args_ptr[i]);
-                        }
-                    }
-                    break;
-                case Stage2ClangArgDebug:
-                    strip = false;
-                    if (strcmp(it.only_arg, "-g") == 0) {
-                        // we handled with strip = false above
-                    } else {
-                        for (size_t i = 0; i < it.other_args_len; i += 1) {
-                            clang_argv.append(it.other_args_ptr[i]);
-                        }
-                    }
-                    break;
-                case Stage2ClangArgSanitize:
-                    if (strcmp(it.only_arg, "undefined") == 0) {
-                        want_sanitize_c = WantCSanitizeEnabled;
-                    } else {
-                        for (size_t i = 0; i < it.other_args_len; i += 1) {
-                            clang_argv.append(it.other_args_ptr[i]);
-                        }
-                    }
-                    break;
-                case Stage2ClangArgLinkerScript:
-                    linker_script = it.only_arg;
-                    break;
-                case Stage2ClangArgVerboseCmds:
-                    verbose_cc = true;
-                    verbose_link = true;
-                    break;
-                case Stage2ClangArgForLinker:
-                    linker_args.append(buf_create_from_str(it.only_arg));
-                    break;
-                case Stage2ClangArgLinkerInputZ:
-                    linker_args.append(buf_create_from_str("-z"));
-                    linker_args.append(buf_create_from_str(it.only_arg));
-                    break;
-                case Stage2ClangArgLibDir:
-                    lib_dirs.append(it.only_arg);
-                    break;
-                case Stage2ClangArgMCpu:
-                    mcpu = it.only_arg;
-                    break;
-                case Stage2ClangArgDepFile:
-                    disable_c_depfile = true;
-                    for (size_t i = 0; i < it.other_args_len; i += 1) {
-                        clang_argv.append(it.other_args_ptr[i]);
-                    }
-                    break;
-                case Stage2ClangArgFrameworkDir:
-                    framework_dirs.append(it.only_arg);
-                    break;
-                case Stage2ClangArgFramework:
-                    frameworks.append(it.only_arg);
-                    break;
-                case Stage2ClangArgNoStdLibInc:
-                    want_native_include_dirs = false;
-                    break;
-            }
-        }
-        // Parse linker args
-        for (size_t i = 0; i < linker_args.length; i += 1) {
-            Buf *arg = linker_args.at(i);
-            if (buf_eql_str(arg, "-soname")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                Buf *soname_buf = linker_args.at(i);
-                override_soname = buf_ptr(soname_buf);
-                // use it as --name
-                // example: libsoundio.so.2
-                size_t prefix = 0;
-                if (buf_starts_with_str(soname_buf, "lib")) {
-                    prefix = 3;
-                }
-                size_t end = buf_len(soname_buf);
-                if (buf_ends_with_str(soname_buf, ".so")) {
-                    end -= 3;
-                } else {
-                    bool found_digit = false;
-                    while (end > 0 && isdigit(buf_ptr(soname_buf)[end - 1])) {
-                        found_digit = true;
-                        end -= 1;
-                    }
-                    if (found_digit && end > 0 && buf_ptr(soname_buf)[end - 1] == '.') {
-                        end -= 1;
-                    } else {
-                        end = buf_len(soname_buf);
-                    }
-                    if (buf_ends_with_str(buf_slice(soname_buf, prefix, end), ".so")) {
-                        end -= 3;
-                    }
-                }
-                out_name = buf_ptr(buf_slice(soname_buf, prefix, end));
-            } else if (buf_eql_str(arg, "-rpath")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                Buf *rpath = linker_args.at(i);
-                rpath_list.append(buf_ptr(rpath));
-            } else if (buf_eql_str(arg, "-I") ||
-                buf_eql_str(arg, "--dynamic-linker") ||
-                buf_eql_str(arg, "-dynamic-linker"))
-            {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                dynamic_linker = buf_ptr(linker_args.at(i));
-            } else if (buf_eql_str(arg, "-E") ||
-                buf_eql_str(arg, "--export-dynamic") ||
-                buf_eql_str(arg, "-export-dynamic"))
-            {
-                rdynamic = true;
-            } else if (buf_eql_str(arg, "--version-script")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                version_script = linker_args.at(i);
-            } else if (buf_starts_with_str(arg, "-O")) {
-                linker_optimization = arg;
-            } else if (buf_eql_str(arg, "--gc-sections")) {
-                linker_gc_sections = OptionalBoolTrue;
-            } else if (buf_eql_str(arg, "--no-gc-sections")) {
-                linker_gc_sections = OptionalBoolFalse;
-            } else if (buf_eql_str(arg, "--allow-shlib-undefined") ||
-                       buf_eql_str(arg, "-allow-shlib-undefined"))
-            {
-                linker_allow_shlib_undefined = OptionalBoolTrue;
-            } else if (buf_eql_str(arg, "--no-allow-shlib-undefined") ||
-                       buf_eql_str(arg, "-no-allow-shlib-undefined"))
-            {
-                linker_allow_shlib_undefined = OptionalBoolFalse;
-            } else if (buf_eql_str(arg, "-Bsymbolic")) {
-                linker_bind_global_refs_locally = OptionalBoolTrue;
-            } else if (buf_eql_str(arg, "-z")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                Buf *z_arg = linker_args.at(i);
-                if (buf_eql_str(z_arg, "nodelete")) {
-                    linker_z_nodelete = true;
-                } else if (buf_eql_str(z_arg, "defs")) {
-                    linker_z_defs = true;
-                } else {
-                    fprintf(stderr, "warning: unsupported linker arg: -z %s\n", buf_ptr(z_arg));
-                }
-            } else if (buf_eql_str(arg, "--major-image-version")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                ver_major = atoi(buf_ptr(linker_args.at(i)));
-            } else if (buf_eql_str(arg, "--minor-image-version")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                ver_minor = atoi(buf_ptr(linker_args.at(i)));
-            } else if (buf_eql_str(arg, "--stack")) {
-                i += 1;
-                if (i >= linker_args.length) {
-                    fprintf(stderr, "expected linker arg after '%s'\n", buf_ptr(arg));
-                    return EXIT_FAILURE;
-                }
-                stack_size_override = atoi(buf_ptr(linker_args.at(i)));
-            } else {
-                fprintf(stderr, "warning: unsupported linker arg: %s\n", buf_ptr(arg));
-            }
-        }
-
-        if (want_sanitize_c == WantCSanitizeEnabled && build_mode == BuildModeFastRelease) {
-            build_mode = BuildModeSafeRelease;
-        }
-
-        if (only_pp_or_asm) {
-            cmd = CmdBuild;
-            out_type = OutTypeObj;
-            emit_bin = false;
-            // Transfer "objects" into c_source_files
-            for (size_t i = 0; i < objects.length; i += 1) {
-                CFile *c_file = heap::c_allocator.create<CFile>();
-                c_file->source_path = objects.at(i);
-                c_source_files.append(c_file);
-            }
-            for (size_t i = 0; i < c_source_files.length; i += 1) {
-                Buf *src_path;
-                if (emit_bin_override_path != nullptr) {
-                    src_path = buf_create_from_str(emit_bin_override_path);
-                } else {
-                    src_path = buf_create_from_str(c_source_files.at(i)->source_path);
-                }
-                Buf basename = BUF_INIT;
-                os_path_split(src_path, nullptr, &basename);
-                c_source_files.at(i)->preprocessor_only_basename = buf_ptr(&basename);
-            }
-        } else if (!c_arg) {
-            cmd = CmdBuild;
-            if (is_shared_lib) {
-                out_type = OutTypeLib;
-            } else {
-                out_type = OutTypeExe;
-            }
-            if (emit_bin_override_path == nullptr) {
-                emit_bin_override_path = "a.out";
-                enable_cache = CacheOptOn;
-            }
-        } else {
-            cmd = CmdBuild;
-            out_type = OutTypeObj;
-        }
-        if (c_source_files.length == 0 && objects.length == 0) {
-            // For example `zig cc` and no args should print the "no input files" message.
-            return ZigClang_main(argc, argv);
-        }
+    } else if (argc >= 2 && strcmp(argv[1], "cc") == 0) {
+        return stage2_cc(argc, argv, false);
+    } else if (argc >= 2 && strcmp(argv[1], "c++") == 0) {
+        return stage2_cc(argc, argv, true);
     } else for (int i = 1; i < argc; i += 1) {
         char *arg = argv[i];
 
@@ -1038,7 +682,6 @@ static int main0(int argc, char **argv) {
                     have_libc = true;
                     link_libs.append("c");
                 } else if (strcmp(l, "c++") == 0 || strcmp(l, "stdc++") == 0) {
-                    have_libcpp = true;
                     link_libs.append("c++");
                 } else {
                     link_libs.append(l);
@@ -1185,7 +828,6 @@ static int main0(int argc, char **argv) {
                         have_libc = true;
                         link_libs.append("c");
                     } else if (strcmp(argv[i], "c++") == 0 || strcmp(argv[i], "stdc++") == 0) {
-                        have_libcpp = true;
                         link_libs.append("c++");
                     } else {
                         link_libs.append(argv[i]);
@@ -1351,15 +993,6 @@ static int main0(int argc, char **argv) {
         return print_error_usage(arg0);
     }
 
-    if (!have_libc && ensure_libc_on_non_freestanding && target.os != OsFreestanding) {
-        have_libc = true;
-        link_libs.append("c");
-    }
-    if (!have_libcpp && ensure_libcpp_on_non_freestanding && target.os != OsFreestanding) {
-        have_libcpp = true;
-        link_libs.append("c++");
-    }
-
     Buf zig_triple_buf = BUF_INIT;
     target_triple_zig(&zig_triple_buf, &target);
 
@@ -1616,20 +1249,10 @@ static int main0(int argc, char **argv) {
             g->system_linker_hack = system_linker_hack;
             g->function_sections = function_sections;
             g->code_model = code_model;
-            g->disable_c_depfile = disable_c_depfile;
 
-            g->linker_optimization = linker_optimization;
-            g->linker_gc_sections = linker_gc_sections;
-            g->linker_allow_shlib_undefined = linker_allow_shlib_undefined;
             g->linker_bind_global_refs_locally = linker_bind_global_refs_locally;
-            g->linker_z_nodelete = linker_z_nodelete;
-            g->linker_z_defs = linker_z_defs;
             g->stack_size_override = stack_size_override;
 
-            if (override_soname) {
-                g->override_soname = buf_create_from_str(override_soname);
-            }
-
             for (size_t i = 0; i < lib_dirs.length; i += 1) {
                 codegen_add_lib_dir(g, lib_dirs.at(i));
             }
@@ -1713,37 +1336,12 @@ static int main0(int argc, char **argv) {
                         buf_replace(g->output_dir, '/', '\\');
 #endif
                         Buf *dest_path = buf_create_from_str(emit_bin_override_path);
-                        Buf *source_path;
-                        if (only_pp_or_asm) {
-                            source_path = buf_alloc();
-                            Buf *pp_only_basename = buf_create_from_str(
-                                    c_source_files.at(0)->preprocessor_only_basename);
-                            os_path_join(g->output_dir, pp_only_basename, source_path);
-
-                        } else {
-                            source_path = &g->bin_file_output_path;
-                        }
+                        Buf *source_path = &g->bin_file_output_path;
                         if ((err = os_update_file(source_path, dest_path))) {
                             fprintf(stderr, "unable to copy %s to %s: %s\n", buf_ptr(source_path),
                                     buf_ptr(dest_path), err_str(err));
                             return main_exit(root_progress_node, EXIT_FAILURE);
                         }
-                    } else if (only_pp_or_asm) {
-#if defined(ZIG_OS_WINDOWS)
-                        buf_replace(g->c_artifact_dir, '/', '\\');
-#endif
-                        // dump the preprocessed output to stdout
-                        for (size_t i = 0; i < c_source_files.length; i += 1) {
-                            Buf *source_path = buf_alloc();
-                            Buf *pp_only_basename = buf_create_from_str(
-                                    c_source_files.at(i)->preprocessor_only_basename);
-                            os_path_join(g->c_artifact_dir, pp_only_basename, source_path);
-                            if ((err = os_dump_file(source_path, stdout))) {
-                                fprintf(stderr, "unable to read %s: %s\n", buf_ptr(source_path),
-                                        err_str(err));
-                                return main_exit(root_progress_node, EXIT_FAILURE);
-                            }
-                        }
                     } else if (g->enable_cache) {
 #if defined(ZIG_OS_WINDOWS)
                         buf_replace(&g->bin_file_output_path, '/', '\\');
src/stage2.cpp
@@ -32,6 +32,11 @@ int stage2_env(int argc, char** argv) {
     stage2_panic(msg, strlen(msg));
 }
 
+int stage2_cc(int argc, char** argv, bool is_cpp) {
+    const char *msg = "stage0 called stage2_cc";
+    stage2_panic(msg, strlen(msg));
+}
+
 void stage2_attach_segfault_handler(void) { }
 
 void stage2_panic(const char *ptr, size_t len) {
@@ -316,16 +321,4 @@ enum Error stage2_detect_native_paths(struct Stage2NativePaths *native_paths) {
     return ErrorNone;
 }
 
-void stage2_clang_arg_iterator(struct Stage2ClangArgIterator *it,
-        size_t argc, char **argv)
-{
-    const char *msg = "stage0 called stage2_clang_arg_iterator";
-    stage2_panic(msg, strlen(msg));
-}
-
-enum Error stage2_clang_arg_next(struct Stage2ClangArgIterator *it) {
-    const char *msg = "stage0 called stage2_clang_arg_next";
-    stage2_panic(msg, strlen(msg));
-}
-
 const bool stage2_is_zig0 = true;
src/stage2.h
@@ -144,6 +144,9 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len);
 // ABI warning
 ZIG_EXTERN_C int stage2_env(int argc, char **argv);
 
+// ABI warning
+ZIG_EXTERN_C int stage2_cc(int argc, char **argv, bool is_cpp);
+
 // ABI warning
 ZIG_EXTERN_C void stage2_attach_segfault_handler(void);
 
@@ -328,60 +331,6 @@ struct Stage2NativePaths {
 // ABI warning
 ZIG_EXTERN_C enum Error stage2_detect_native_paths(struct Stage2NativePaths *native_paths);
 
-// ABI warning
-enum Stage2ClangArg {
-    Stage2ClangArgTarget,
-    Stage2ClangArgO,
-    Stage2ClangArgC,
-    Stage2ClangArgOther,
-    Stage2ClangArgPositional,
-    Stage2ClangArgL,
-    Stage2ClangArgIgnore,
-    Stage2ClangArgDriverPunt,
-    Stage2ClangArgPIC,
-    Stage2ClangArgNoPIC,
-    Stage2ClangArgNoStdLib,
-    Stage2ClangArgNoStdLibCpp,
-    Stage2ClangArgShared,
-    Stage2ClangArgRDynamic,
-    Stage2ClangArgWL,
-    Stage2ClangArgPreprocessOrAsm,
-    Stage2ClangArgOptimize,
-    Stage2ClangArgDebug,
-    Stage2ClangArgSanitize,
-    Stage2ClangArgLinkerScript,
-    Stage2ClangArgVerboseCmds,
-    Stage2ClangArgForLinker,
-    Stage2ClangArgLinkerInputZ,
-    Stage2ClangArgLibDir,
-    Stage2ClangArgMCpu,
-    Stage2ClangArgDepFile,
-    Stage2ClangArgFrameworkDir,
-    Stage2ClangArgFramework,
-    Stage2ClangArgNoStdLibInc,
-};
-
-// ABI warning
-struct Stage2ClangArgIterator {
-    bool has_next;
-    enum Stage2ClangArg kind;
-    const char *only_arg;
-    const char *second_arg;
-    const char **other_args_ptr;
-    size_t other_args_len;
-    const char **argv_ptr;
-    size_t argv_len;
-    size_t next_index;
-    size_t root_args;
-};
-
-// ABI warning
-ZIG_EXTERN_C void stage2_clang_arg_iterator(struct Stage2ClangArgIterator *it,
-        size_t argc, char **argv);
-
-// ABI warning
-ZIG_EXTERN_C enum Error stage2_clang_arg_next(struct Stage2ClangArgIterator *it);
-
 // ABI warning
 ZIG_EXTERN_C const bool stage2_is_zig0;
 
src-self-hosted/clang_options.zig
@@ -7,9 +7,7 @@ pub const CliArg = struct {
     name: []const u8,
     syntax: Syntax,
 
-    /// TODO we're going to want to change this when we start shipping self-hosted because this causes
-    /// all the functions in stage2.zig to get exported.
-    zig_equivalent: @import("stage2.zig").ClangArgIterator.ZigEquivalent,
+    zig_equivalent: @import("main.zig").ClangArgIterator.ZigEquivalent,
 
     /// Prefixed by "-"
     pd1: bool = false,
src-self-hosted/main.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const assert = std.debug.assert;
 const io = std.io;
 const fs = std.fs;
 const mem = std.mem;
@@ -11,6 +12,13 @@ const link = @import("link.zig");
 const Package = @import("Package.zig");
 const zir = @import("zir.zig");
 const build_options = @import("build_options");
+const warn = std.log.warn;
+const info = std.log.info;
+
+fn fatal(comptime format: []const u8, args: anytype) noreturn {
+    std.log.emerg(format, args);
+    process.exit(1);
+}
 
 pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
 
@@ -28,9 +36,11 @@ const usage =
     \\  build-exe  [source]      Create executable from source or object files
     \\  build-lib  [source]      Create library from source or object files
     \\  build-obj  [source]      Create object from source or assembly
+    \\  cc                       Use Zig as a drop-in C compiler
+    \\  c++                      Use Zig as a drop-in C++ compiler
+    \\  env                      Print lib path, std path, compiler id and version
     \\  fmt        [source]      Parse file and render in canonical zig format
     \\  targets                  List available compilation targets
-    \\  env                      Print lib path, std path, compiler id and version
     \\  version                  Print version number and exit
     \\  zen                      Print zen of zig and exit
     \\
@@ -84,11 +94,19 @@ pub fn main() !void {
     const cmd = args[1];
     const cmd_args = args[2..];
     if (mem.eql(u8, cmd, "build-exe")) {
-        return buildOutputType(gpa, arena, cmd_args, .Exe);
+        return buildOutputType(gpa, arena, args, .{ .build = .Exe });
     } else if (mem.eql(u8, cmd, "build-lib")) {
-        return buildOutputType(gpa, arena, cmd_args, .Lib);
+        return buildOutputType(gpa, arena, args, .{ .build = .Lib });
     } else if (mem.eql(u8, cmd, "build-obj")) {
-        return buildOutputType(gpa, arena, cmd_args, .Obj);
+        return buildOutputType(gpa, arena, args, .{ .build = .Obj });
+    } else if (mem.eql(u8, cmd, "cc")) {
+        return buildOutputType(gpa, arena, args, .cc);
+    } else if (mem.eql(u8, cmd, "c++")) {
+        return buildOutputType(gpa, arena, args, .cpp);
+    } else if (mem.eql(u8, cmd, "clang") or
+        mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))
+    {
+        return punt_to_clang(arena, args);
     } else if (mem.eql(u8, cmd, "fmt")) {
         return cmdFmt(gpa, cmd_args);
     } else if (mem.eql(u8, cmd, "targets")) {
@@ -147,6 +165,8 @@ const usage_build_generic =
     \\    ReleaseFast             Optimizations on, safety off
     \\    ReleaseSafe             Optimizations on, safety on
     \\    ReleaseSmall            Optimize for small binary, safety off
+    \\  -fPIC                     Force-enable Position Independent Code
+    \\  -fno-PIC                  Force-disable Position Independent Code
     \\  --dynamic                 Force output to be dynamically linked
     \\  --strip                   Exclude debug symbols
     \\  -ofmt=[mode]              Override target object format
@@ -158,11 +178,19 @@ const usage_build_generic =
     \\    macho  (planned)        macOS relocatables
     \\    hex    (planned)        Intel IHEX
     \\    raw    (planned)        Dump machine code directly
+    \\  -dirafter [dir]           Add directory to AFTER include search path
+    \\  -isystem  [dir]           Add directory to SYSTEM include search path
+    \\  -I[dir]                   Add directory to include search path
+    \\  -D[macro]=[value]         Define C [macro] to [value] (1 if [value] omitted)
     \\
     \\Link Options:
     \\  -l[lib], --library [lib]  Link against system library
+    \\  -L[d], --library-directory [d] Add a directory to the library search path
+    \\  -T[script]                Use a custom linker script
     \\  --dynamic-linker [path]   Set the dynamic interpreter path (usually ld.so)
     \\  --version [ver]           Dynamic library semver
+    \\  -rdynamic                 Add all symbols to the dynamic symbol table
+    \\  -rpath [path]             Add directory to the runtime library search path
     \\
     \\Debug Options (Zig Compiler Development):
     \\  -ftime-report             Print timing diagnostics
@@ -181,11 +209,15 @@ const Emit = union(enum) {
     yes: []const u8,
 };
 
-fn buildOutputType(
+pub fn buildOutputType(
     gpa: *Allocator,
     arena: *Allocator,
-    args: []const []const u8,
-    output_mode: std.builtin.OutputMode,
+    all_args: []const []const u8,
+    arg_mode: union(enum) {
+        build: std.builtin.OutputMode,
+        cc,
+        cpp,
+    },
 ) !void {
     var color: Color = .Auto;
     var build_mode: std.builtin.Mode = .Debug;
@@ -194,6 +226,7 @@ fn buildOutputType(
     var root_src_file: ?[]const u8 = null;
     var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 };
     var strip = false;
+    var emit_h = true;
     var watch = false;
     var debug_tokenize = false;
     var debug_ast_tree = false;
@@ -201,6 +234,7 @@ fn buildOutputType(
     var debug_link = false;
     var debug_ir = false;
     var debug_codegen = false;
+    var debug_cc = false;
     var time_report = false;
     var emit_bin: Emit = .yes_default_path;
     var emit_zir: Emit = .no;
@@ -208,11 +242,57 @@ fn buildOutputType(
     var target_mcpu: ?[]const u8 = null;
     var target_dynamic_linker: ?[]const u8 = null;
     var target_ofmt: ?[]const u8 = null;
+    var output_mode: std.builtin.OutputMode = undefined;
+    var ensure_libc_on_non_freestanding = false;
+    var ensure_libcpp_on_non_freestanding = false;
+    var have_libc = false;
+    var have_libcpp = false;
+    var want_native_include_dirs = false;
+    var enable_cache: ?bool = null;
+    var want_pic: ?bool = null;
+    var want_sanitize_c: ?bool = null;
+    var rdynamic: bool = false;
+    var only_pp_or_asm = false;
+    var linker_script: ?[]const u8 = null;
+    var version_script: ?[]const u8 = null;
+    var disable_c_depfile = false;
+    var override_soname: ?[]const u8 = null;
+    var linker_optimization: ?[]const u8 = null;
+    var linker_gc_sections: ?bool = null;
+    var linker_allow_shlib_undefined: ?bool = null;
+    var linker_bind_global_refs_locally: ?bool = null;
+    var linker_z_nodelete = false;
+    var linker_z_defs = false;
+    var stack_size_override: u64 = 0;
 
     var system_libs = std.ArrayList([]const u8).init(gpa);
     defer system_libs.deinit();
 
-    {
+    var clang_argv = std.ArrayList([]const u8).init(gpa);
+    defer clang_argv.deinit();
+
+    var lib_dirs = std.ArrayList([]const u8).init(gpa);
+    defer lib_dirs.deinit();
+
+    var rpath_list = std.ArrayList([]const u8).init(gpa);
+    defer rpath_list.deinit();
+
+    var c_source_files = std.ArrayList([]const u8).init(gpa);
+    defer c_source_files.deinit();
+
+    var link_objects = std.ArrayList([]const u8).init(gpa);
+    defer link_objects.deinit();
+
+    var framework_dirs = std.ArrayList([]const u8).init(gpa);
+    defer framework_dirs.deinit();
+
+    var frameworks = std.ArrayList([]const u8).init(gpa);
+    defer frameworks.deinit();
+
+    if (arg_mode == .build) {
+        output_mode = arg_mode.build;
+
+        const args = all_args[2..];
         var i: usize = 0;
         while (i < args.len) : (i += 1) {
             const arg = args[i];
@@ -222,8 +302,7 @@ fn buildOutputType(
                     process.exit(0);
                 } else if (mem.eql(u8, arg, "--color")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected [auto|on|off] after --color\n", .{});
-                        process.exit(1);
+                        fatal("expected [auto|on|off] after --color", .{});
                     }
                     i += 1;
                     const next_arg = args[i];
@@ -234,13 +313,11 @@ fn buildOutputType(
                     } else if (mem.eql(u8, next_arg, "off")) {
                         color = .Off;
                     } else {
-                        std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
-                        process.exit(1);
+                        fatal("expected [auto|on|off] after --color, found '{}'", .{next_arg});
                     }
                 } else if (mem.eql(u8, arg, "--mode")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
-                        process.exit(1);
+                        fatal("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode", .{});
                     }
                     i += 1;
                     const next_arg = args[i];
@@ -253,44 +330,66 @@ fn buildOutputType(
                     } else if (mem.eql(u8, next_arg, "ReleaseSmall")) {
                         build_mode = .ReleaseSmall;
                     } else {
-                        std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
-                        process.exit(1);
+                        fatal("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'", .{next_arg});
                     }
+                } else if (mem.eql(u8, arg, "--stack")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    stack_size_override = std.fmt.parseInt(u64, args[i], 10) catch |err| {
+                        fatal("unable to parse '{}': {}", .{ arg, @errorName(err) });
+                    };
                 } else if (mem.eql(u8, arg, "--name")) {
-                    if (i + 1 >= args.len) {
-                        std.debug.print("expected parameter after --name\n", .{});
-                        process.exit(1);
-                    }
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
                     i += 1;
                     provided_name = args[i];
-                } else if (mem.eql(u8, arg, "--library")) {
-                    if (i + 1 >= args.len) {
-                        std.debug.print("expected parameter after --library\n", .{});
-                        process.exit(1);
-                    }
+                } else if (mem.eql(u8, arg, "-rpath")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    try rpath_list.append(args[i]);
+                } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    try lib_dirs.append(args[i]);
+                } else if (mem.eql(u8, arg, "-T")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    linker_script = args[i];
+                } else if (mem.eql(u8, arg, "--version-script")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    version_script = args[i];
+                } else if (mem.eql(u8, arg, "--library") or mem.eql(u8, arg, "-l")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    // We don't know whether this library is part of libc or libc++ until we resolve the target.
+                    // So we simply append to the list for now.
                     i += 1;
                     try system_libs.append(args[i]);
+                } else if (mem.eql(u8, arg, "-D") or
+                    mem.eql(u8, arg, "-isystem") or
+                    mem.eql(u8, arg, "-I") or
+                    mem.eql(u8, arg, "-dirafter"))
+                {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    try clang_argv.append(arg);
+                    try clang_argv.append(args[i]);
                 } else if (mem.eql(u8, arg, "--version")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected parameter after --version\n", .{});
-                        process.exit(1);
+                        fatal("expected parameter after --version", .{});
                     }
                     i += 1;
                     version = std.builtin.Version.parse(args[i]) catch |err| {
-                        std.debug.print("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
-                        process.exit(1);
+                        fatal("unable to parse --version '{}': {}", .{ args[i], @errorName(err) });
                     };
                 } else if (mem.eql(u8, arg, "-target")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected parameter after -target\n", .{});
-                        process.exit(1);
+                        fatal("expected parameter after -target", .{});
                     }
                     i += 1;
                     target_arch_os_abi = args[i];
                 } else if (mem.eql(u8, arg, "-mcpu")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected parameter after -mcpu\n", .{});
-                        process.exit(1);
+                        fatal("expected parameter after -mcpu", .{});
                     }
                     i += 1;
                     target_mcpu = args[i];
@@ -300,8 +399,7 @@ fn buildOutputType(
                     target_mcpu = arg["-mcpu=".len..];
                 } else if (mem.eql(u8, arg, "--dynamic-linker")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected parameter after --dynamic-linker\n", .{});
-                        process.exit(1);
+                        fatal("expected parameter after --dynamic-linker", .{});
                     }
                     i += 1;
                     target_dynamic_linker = args[i];
@@ -309,6 +407,12 @@ fn buildOutputType(
                     watch = true;
                 } else if (mem.eql(u8, arg, "-ftime-report")) {
                     time_report = true;
+                } else if (mem.eql(u8, arg, "-fPIC")) {
+                    want_pic = true;
+                } else if (mem.eql(u8, arg, "-fno-PIC")) {
+                    want_pic = false;
+                } else if (mem.eql(u8, arg, "-rdynamic")) {
+                    rdynamic = true;
                 } else if (mem.eql(u8, arg, "-femit-bin")) {
                     emit_bin = .yes_default_path;
                 } else if (mem.startsWith(u8, arg, "-femit-bin=")) {
@@ -327,6 +431,8 @@ fn buildOutputType(
                     link_mode = .Static;
                 } else if (mem.eql(u8, arg, "--strip")) {
                     strip = true;
+                } else if (mem.eql(u8, arg, "-Bsymbolic")) {
+                    linker_bind_global_refs_locally = true;
                 } else if (mem.eql(u8, arg, "--debug-tokenize")) {
                     debug_tokenize = true;
                 } else if (mem.eql(u8, arg, "--debug-ast-tree")) {
@@ -339,44 +445,321 @@ fn buildOutputType(
                     debug_ir = true;
                 } else if (mem.eql(u8, arg, "--debug-codegen")) {
                     debug_codegen = true;
+                } else if (mem.eql(u8, arg, "--debug-cc")) {
+                    debug_cc = true;
+                } else if (mem.startsWith(u8, arg, "-T")) {
+                    linker_script = arg[2..];
+                } else if (mem.startsWith(u8, arg, "-L")) {
+                    try lib_dirs.append(arg[2..]);
                 } else if (mem.startsWith(u8, arg, "-l")) {
+                    // We don't know whether this library is part of libc or libc++ until we resolve the target.
+                    // So we simply append to the list for now.
                     try system_libs.append(arg[2..]);
+                } else if (mem.startsWith(u8, arg, "-D") or
+                    mem.startsWith(u8, arg, "-I"))
+                {
+                    try clang_argv.append(arg);
                 } else {
-                    std.debug.print("unrecognized parameter: '{}'\n", .{arg});
-                    process.exit(1);
+                    fatal("unrecognized parameter: '{}'", .{arg});
                 }
-            } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {
-                std.debug.print("assembly files not supported yet\n", .{});
-                process.exit(1);
             } else if (mem.endsWith(u8, arg, ".o") or
                 mem.endsWith(u8, arg, ".obj") or
                 mem.endsWith(u8, arg, ".a") or
                 mem.endsWith(u8, arg, ".lib"))
             {
-                std.debug.print("object files and static libraries not supported yet\n", .{});
-                process.exit(1);
-            } else if (mem.endsWith(u8, arg, ".c") or
-                mem.endsWith(u8, arg, ".cpp"))
-            {
-                std.debug.print("compilation of C and C++ source code requires LLVM extensions which are not implemented yet\n", .{});
-                process.exit(1);
+                try link_objects.append(arg);
+            } else if (hasAsmExt(arg) or hasCExt(arg) or hasCppExt(arg)) {
+                try c_source_files.append(arg);
             } else if (mem.endsWith(u8, arg, ".so") or
                 mem.endsWith(u8, arg, ".dylib") or
                 mem.endsWith(u8, arg, ".dll"))
             {
-                std.debug.print("linking against dynamic libraries not yet supported\n", .{});
-                process.exit(1);
+                fatal("linking against dynamic libraries not yet supported", .{});
             } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {
                 if (root_src_file) |other| {
-                    std.debug.print("found another zig file '{}' after root source file '{}'\n", .{ arg, other });
-                    process.exit(1);
+                    fatal("found another zig file '{}' after root source file '{}'", .{ arg, other });
                 } else {
                     root_src_file = arg;
                 }
             } else {
-                std.debug.print("unrecognized file extension of parameter '{}'\n", .{arg});
+                fatal("unrecognized file extension of parameter '{}'", .{arg});
+            }
+        }
+    } else {
+        if (!build_options.have_llvm)
+            fatal("`zig cc` and `zig c++` unavailable: compiler not built with LLVM extensions enabled", .{});
+        emit_h = false;
+        strip = true;
+        ensure_libc_on_non_freestanding = true;
+        ensure_libcpp_on_non_freestanding = arg_mode == .cpp;
+        want_native_include_dirs = true;
+
+        var c_arg = false;
+        var is_shared_lib = false;
+        var linker_args = std.ArrayList([]const u8).init(arena);
+        var it = ClangArgIterator.init(arena, all_args);
+        while (it.has_next) {
+            it.next() catch |err| {
+                fatal("unable to parse command line parameters: {}", .{@errorName(err)});
+            };
+            switch (it.zig_equivalent) {
+                .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown
+                .o => {
+                    // -o
+                    emit_bin = .{ .yes = it.only_arg };
+                    enable_cache = true;
+                },
+                .c => c_arg = true, // -c
+                .other => {
+                    try clang_argv.appendSlice(it.other_args);
+                },
+                .positional => {
+                    const file_ext = classify_file_ext(mem.spanZ(it.only_arg));
+                    switch (file_ext) {
+                        .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(it.only_arg),
+                        .unknown => try link_objects.append(it.only_arg),
+                    }
+                },
+                .l => {
+                    // -l
+                    // We don't know whether this library is part of libc or libc++ until we resolve the target.
+                    // So we simply append to the list for now.
+                    try system_libs.append(it.only_arg);
+                },
+                .ignore => {},
+                .driver_punt => {
+                    // Never mind what we're doing, just pass the args directly. For example --help.
+                    return punt_to_clang(arena, all_args);
+                },
+                .pic => want_pic = true,
+                .no_pic => want_pic = false,
+                .nostdlib => ensure_libc_on_non_freestanding = false,
+                .nostdlib_cpp => ensure_libcpp_on_non_freestanding = false,
+                .shared => {
+                    link_mode = .Dynamic;
+                    is_shared_lib = true;
+                },
+                .rdynamic => rdynamic = true,
+                .wl => {
+                    var split_it = mem.split(it.only_arg, ",");
+                    @breakpoint(); // TODO the first arg is empty string right? skip past that.
+                    while (split_it.next()) |linker_arg| {
+                        try linker_args.append(linker_arg);
+                    }
+                },
+                .pp_or_asm => {
+                    // This handles both -E and -S.
+                    only_pp_or_asm = true;
+                    try clang_argv.appendSlice(it.other_args);
+                },
+                .optimize => {
+                    // Alright, what release mode do they want?
+                    if (mem.eql(u8, it.only_arg, "Os")) {
+                        build_mode = .ReleaseSmall;
+                    } else if (mem.eql(u8, it.only_arg, "O2") or
+                        mem.eql(u8, it.only_arg, "O3") or
+                        mem.eql(u8, it.only_arg, "O4"))
+                    {
+                        build_mode = .ReleaseFast;
+                    } else if (mem.eql(u8, it.only_arg, "Og") or
+                        mem.eql(u8, it.only_arg, "O0"))
+                    {
+                        build_mode = .Debug;
+                    } else {
+                        try clang_argv.appendSlice(it.other_args);
+                    }
+                },
+                .debug => {
+                    strip = false;
+                    if (mem.eql(u8, it.only_arg, "-g")) {
+                        // We handled with strip = false above.
+                    } else {
+                        try clang_argv.appendSlice(it.other_args);
+                    }
+                },
+                .sanitize => {
+                    if (mem.eql(u8, it.only_arg, "undefined")) {
+                        want_sanitize_c = true;
+                    } else {
+                        try clang_argv.appendSlice(it.other_args);
+                    }
+                },
+                .linker_script => linker_script = it.only_arg,
+                .verbose_cmds => {
+                    debug_cc = true;
+                    debug_link = true;
+                },
+                .for_linker => try linker_args.append(it.only_arg),
+                .linker_input_z => {
+                    try linker_args.append("-z");
+                    try linker_args.append(it.only_arg);
+                },
+                .lib_dir => try lib_dirs.append(it.only_arg),
+                .mcpu => target_mcpu = it.only_arg,
+                .dep_file => {
+                    disable_c_depfile = true;
+                    try clang_argv.appendSlice(it.other_args);
+                },
+                .framework_dir => try framework_dirs.append(it.only_arg),
+                .framework => try frameworks.append(it.only_arg),
+                .nostdlibinc => want_native_include_dirs = false,
+            }
+        }
+        // Parse linker args.
+        var i: usize = 0;
+        while (i < linker_args.items.len) : (i += 1) {
+            const arg = linker_args.items[i];
+            if (mem.eql(u8, arg, "-soname")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                const soname = linker_args.items[i];
+                override_soname = soname;
+                // Use it as --name.
+                // Example: libsoundio.so.2
+                var prefix: usize = 0;
+                if (mem.startsWith(u8, soname, "lib")) {
+                    prefix = 3;
+                }
+                var end: usize = soname.len;
+                if (mem.endsWith(u8, soname, ".so")) {
+                    end -= 3;
+                } else {
+                    var found_digit = false;
+                    while (end > 0 and std.ascii.isDigit(soname[end - 1])) {
+                        found_digit = true;
+                        end -= 1;
+                    }
+                    if (found_digit and end > 0 and soname[end - 1] == '.') {
+                        end -= 1;
+                    } else {
+                        end = soname.len;
+                    }
+                    if (mem.endsWith(u8, soname[prefix..end], ".so")) {
+                        end -= 3;
+                    }
+                }
+                provided_name = soname[prefix..end];
+            } else if (mem.eql(u8, arg, "-rpath")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                try rpath_list.append(linker_args.items[i]);
+            } else if (mem.eql(u8, arg, "-I") or
+                mem.eql(u8, arg, "--dynamic-linker") or
+                mem.eql(u8, arg, "-dynamic-linker"))
+            {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                target_dynamic_linker = linker_args.items[i];
+            } else if (mem.eql(u8, arg, "-E") or
+                mem.eql(u8, arg, "--export-dynamic") or
+                mem.eql(u8, arg, "-export-dynamic"))
+            {
+                rdynamic = true;
+            } else if (mem.eql(u8, arg, "--version-script")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                version_script = linker_args.items[i];
+            } else if (mem.startsWith(u8, arg, "-O")) {
+                linker_optimization = arg;
+            } else if (mem.eql(u8, arg, "--gc-sections")) {
+                linker_gc_sections = true;
+            } else if (mem.eql(u8, arg, "--no-gc-sections")) {
+                linker_gc_sections = false;
+            } else if (mem.eql(u8, arg, "--allow-shlib-undefined") or
+                mem.eql(u8, arg, "-allow-shlib-undefined"))
+            {
+                linker_allow_shlib_undefined = true;
+            } else if (mem.eql(u8, arg, "--no-allow-shlib-undefined") or
+                mem.eql(u8, arg, "-no-allow-shlib-undefined"))
+            {
+                linker_allow_shlib_undefined = false;
+            } else if (mem.eql(u8, arg, "-Bsymbolic")) {
+                linker_bind_global_refs_locally = true;
+            } else if (mem.eql(u8, arg, "-z")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                const z_arg = linker_args.items[i];
+                if (mem.eql(u8, z_arg, "nodelete")) {
+                    linker_z_nodelete = true;
+                } else if (mem.eql(u8, z_arg, "defs")) {
+                    linker_z_defs = true;
+                } else {
+                    warn("unsupported linker arg: -z {}", .{z_arg});
+                }
+            } else if (mem.eql(u8, arg, "--major-image-version")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                version.major = std.fmt.parseInt(u32, linker_args.items[i], 10) catch |err| {
+                    fatal("unable to parse '{}': {}", .{ arg, @errorName(err) });
+                };
+            } else if (mem.eql(u8, arg, "--minor-image-version")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                version.minor = std.fmt.parseInt(u32, linker_args.items[i], 10) catch |err| {
+                    fatal("unable to parse '{}': {}", .{ arg, @errorName(err) });
+                };
+            } else if (mem.eql(u8, arg, "--stack")) {
+                i += 1;
+                if (i >= linker_args.items.len) {
+                    fatal("expected linker arg after '{}'", .{arg});
+                }
+                stack_size_override = std.fmt.parseInt(u64, linker_args.items[i], 10) catch |err| {
+                    fatal("unable to parse '{}': {}", .{ arg, @errorName(err) });
+                };
+            } else {
+                warn("unsupported linker arg: {}", .{arg});
             }
         }
+
+        if (want_sanitize_c == true and build_mode == .ReleaseFast) {
+            build_mode = .ReleaseSafe;
+        }
+
+        if (only_pp_or_asm) {
+            output_mode = .Obj;
+            fatal("TODO implement using zig cc as a preprocessor", .{});
+            //// Transfer "link_objects" into c_source_files so that all those
+            //// args make it onto the command line.
+            //try c_source_files.appendSlice(link_objects.items);
+            //for (c_source_files.items) |c_source_file| {
+            //    const src_path = switch (emit_bin) {
+            //        .yes => |p| p,
+            //        else => c_source_file.source_path,
+            //    };
+            //    const basename = std.fs.path.basename(src_path);
+            //    c_source_file.preprocessor_only_basename = basename;
+            //}
+            //emit_bin = .no;
+        } else if (!c_arg) {
+            output_mode = if (is_shared_lib) .Lib else .Exe;
+            switch (emit_bin) {
+                .no, .yes_default_path => {
+                    emit_bin = .{ .yes = "a.out" };
+                    enable_cache = true;
+                },
+                .yes => {},
+            }
+        } else {
+            output_mode = .Obj;
+        }
+        if (c_source_files.items.len == 0 and link_objects.items.len == 0) {
+            // For example `zig cc` and no args should print the "no input files" message.
+            return punt_to_clang(arena, all_args);
+        }
     }
 
     const root_name = if (provided_name) |n| n else blk: {
@@ -385,16 +768,10 @@ fn buildOutputType(
             var it = mem.split(basename, ".");
             break :blk it.next() orelse basename;
         } else {
-            std.debug.print("--name [name] not provided and unable to infer\n", .{});
-            process.exit(1);
+            fatal("--name [name] not provided and unable to infer", .{});
         }
     };
 
-    if (system_libs.items.len != 0) {
-        std.debug.print("linking against system libraries not yet supported\n", .{});
-        process.exit(1);
-    }
-
     var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
     const cross_target = std.zig.CrossTarget.parse(.{
         .arch_os_abi = target_arch_os_abi,
@@ -429,17 +806,67 @@ fn buildOutputType(
         else => |e| return e,
     };
 
-    var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target);
+    const target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target);
     if (target_info.cpu_detection_unimplemented) {
         // TODO We want to just use detected_info.target but implementing
         // CPU model & feature detection is todo so here we rely on LLVM.
-        std.debug.print("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
-        process.exit(1);
+        fatal("CPU features detection is not yet available for this system without LLVM extensions", .{});
+    }
+
+    if (target_info.target.os.tag != .freestanding) {
+        if (ensure_libc_on_non_freestanding)
+            have_libc = true;
+        if (ensure_libcpp_on_non_freestanding)
+            have_libcpp = true;
+    }
+
+    // Now that we have target info, we can find out if any of the system libraries
+    // are part of libc or libc++. We remove them from the list and communicate their
+    // existence via flags instead.
+    {
+        var i: usize = 0;
+        while (i < system_libs.items.len) {
+            const lib_name = system_libs.items[i];
+            if (is_libc_lib_name(target_info.target, lib_name)) {
+                have_libc = true;
+                _ = system_libs.orderedRemove(i);
+                continue;
+            }
+            if (is_libcpp_lib_name(target_info.target, lib_name)) {
+                have_libcpp = true;
+                _ = system_libs.orderedRemove(i);
+                continue;
+            }
+            i += 1;
+        }
+    }
+
+    if (cross_target.isNativeOs() and (system_libs.items.len != 0 or want_native_include_dirs)) {
+        const paths = std.zig.system.NativePaths.detect(arena) catch |err| {
+            fatal("unable to detect native system paths: {}", .{@errorName(err)});
+        };
+        for (paths.warnings.items) |warning| {
+            warn("{}", .{warning});
+        }
+        try clang_argv.ensureCapacity(clang_argv.items.len + paths.include_dirs.items.len * 2);
+        for (paths.include_dirs.items) |include_dir| {
+            clang_argv.appendAssumeCapacity("-isystem");
+            clang_argv.appendAssumeCapacity(include_dir);
+        }
+        for (paths.lib_dirs.items) |lib_dir| {
+            try lib_dirs.append(lib_dir);
+        }
+        for (paths.rpaths.items) |rpath| {
+            try rpath_list.append(rpath);
+        }
+    }
+
+    if (system_libs.items.len != 0) {
+        fatal("linking against system libraries not yet supported", .{});
     }
 
     const src_path = root_src_file orelse {
-        std.debug.print("expected at least one file argument", .{});
-        process.exit(1);
+        fatal("expected at least one file argument", .{});
     };
 
     const object_format: ?std.Target.ObjectFormat = blk: {
@@ -461,15 +888,13 @@ fn buildOutputType(
         } else if (mem.eql(u8, ofmt, "raw")) {
             break :blk .raw;
         } else {
-            std.debug.print("unsupported object format: {}", .{ofmt});
-            process.exit(1);
+            fatal("unsupported object format: {}", .{ofmt});
         }
     };
 
     const bin_path = switch (emit_bin) {
         .no => {
-            std.debug.print("-fno-emit-bin not supported yet", .{});
-            process.exit(1);
+            fatal("-fno-emit-bin not supported yet", .{});
         },
         .yes_default_path => if (object_format != null and object_format.? == .c)
             try std.fmt.allocPrint(arena, "{}.c", .{root_name})
@@ -515,6 +940,11 @@ fn buildOutputType(
 
     try updateModule(gpa, &module, zir_out_path);
 
+    if (build_options.have_llvm and only_pp_or_asm) {
+        // this may include dumping the output to stdout
+        fatal("TODO: implement `zig cc` when using it as a preprocessor", .{});
+    }
+
     while (watch) {
         try stderr.print("๐ŸฆŽ ", .{});
         if (output_mode == .Exe) {
@@ -562,7 +992,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo
             });
         }
     } else {
-        std.log.scoped(.compiler).info("Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms});
+        info("Update completed in {} ms", .{update_nanos / std.time.ns_per_ms});
     }
 
     if (zir_out_path) |zop| {
@@ -631,8 +1061,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
                     process.exit(0);
                 } else if (mem.eql(u8, arg, "--color")) {
                     if (i + 1 >= args.len) {
-                        std.debug.print("expected [auto|on|off] after --color\n", .{});
-                        process.exit(1);
+                        fatal("expected [auto|on|off] after --color", .{});
                     }
                     i += 1;
                     const next_arg = args[i];
@@ -643,16 +1072,14 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
                     } else if (mem.eql(u8, next_arg, "off")) {
                         color = .Off;
                     } else {
-                        std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
-                        process.exit(1);
+                        fatal("expected [auto|on|off] after --color, found '{}'", .{next_arg});
                     }
                 } else if (mem.eql(u8, arg, "--stdin")) {
                     stdin_flag = true;
                 } else if (mem.eql(u8, arg, "--check")) {
                     check_flag = true;
                 } else {
-                    std.debug.print("unrecognized parameter: '{}'", .{arg});
-                    process.exit(1);
+                    fatal("unrecognized parameter: '{}'", .{arg});
                 }
             } else {
                 try input_files.append(arg);
@@ -662,8 +1089,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
 
     if (stdin_flag) {
         if (input_files.items.len != 0) {
-            std.debug.print("cannot use --stdin with positional arguments\n", .{});
-            process.exit(1);
+            fatal("cannot use --stdin with positional arguments", .{});
         }
 
         const stdin = io.getStdIn().inStream();
@@ -672,8 +1098,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
         defer gpa.free(source_code);
 
         const tree = std.zig.parse(gpa, source_code) catch |err| {
-            std.debug.print("error parsing stdin: {}\n", .{err});
-            process.exit(1);
+            fatal("error parsing stdin: {}", .{err});
         };
         defer tree.deinit();
 
@@ -695,8 +1120,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
     }
 
     if (input_files.items.len == 0) {
-        std.debug.print("expected at least one source file argument\n", .{});
-        process.exit(1);
+        fatal("expected at least one source file argument", .{});
     }
 
     var fmt = Fmt{
@@ -712,8 +1136,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
     for (input_files.span()) |file_path| {
         // Get the real path here to avoid Windows failing on relative file paths with . or .. in them.
         const real_path = fs.realpathAlloc(gpa, file_path) catch |err| {
-            std.debug.print("unable to open '{}': {}\n", .{ file_path, err });
-            process.exit(1);
+            fatal("unable to open '{}': {}", .{ file_path, err });
         };
         defer gpa.free(real_path);
 
@@ -752,7 +1175,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_
     fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
         error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
         else => {
-            std.debug.print("unable to format '{}': {}\n", .{ file_path, err });
+            warn("unable to format '{}': {}", .{ file_path, err });
             fmt.any_error = true;
             return;
         },
@@ -783,7 +1206,7 @@ fn fmtPathDir(
                 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
             } else {
                 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
-                    std.debug.print("unable to format '{}': {}\n", .{ full_path, err });
+                    warn("unable to format '{}': {}", .{ full_path, err });
                     fmt.any_error = true;
                     return;
                 };
@@ -841,6 +1264,7 @@ fn fmtPathFile(
     if (check_mode) {
         const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
         if (anything_changed) {
+            // TODO this should output to stdout instead of stderr.
             std.debug.print("{}\n", .{file_path});
             fmt.any_error = true;
         }
@@ -858,6 +1282,7 @@ fn fmtPathFile(
 
         try af.file.writeAll(fmt.out_buffer.items);
         try af.finish();
+        // TODO this should output to stdout instead of stderr.
         std.debug.print("{}\n", .{file_path});
     }
 }
@@ -925,3 +1350,350 @@ pub const info_zen =
     \\
     \\
 ;
+
+const FileExt = enum {
+    c,
+    cpp,
+    h,
+    ll,
+    bc,
+    assembly,
+    unknown,
+};
+
+fn hasCExt(filename: []const u8) bool {
+    return mem.endsWith(u8, filename, ".c");
+}
+
+fn hasCppExt(filename: []const u8) bool {
+    return mem.endsWith(u8, filename, ".C") or
+        mem.endsWith(u8, filename, ".cc") or
+        mem.endsWith(u8, filename, ".cpp") or
+        mem.endsWith(u8, filename, ".cxx");
+}
+
+fn hasAsmExt(filename: []const u8) bool {
+    return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S");
+}
+
+fn classify_file_ext(filename: []const u8) FileExt {
+    if (hasCExt(filename)) {
+        return .c;
+    } else if (hasCppExt(filename)) {
+        return .cpp;
+    } else if (mem.endsWith(u8, filename, ".ll")) {
+        return .ll;
+    } else if (mem.endsWith(u8, filename, ".bc")) {
+        return .bc;
+    } else if (hasAsmExt(filename)) {
+        return .assembly;
+    } else if (mem.endsWith(u8, filename, ".h")) {
+        return .h;
+    } else {
+        // TODO look for .so, .so.X, .so.X.Y, .so.X.Y.Z
+        return .unknown;
+    }
+}
+
+extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
+
+/// TODO make it so the return value can be !noreturn
+fn punt_to_clang(arena: *Allocator, args: []const []const u8) error{OutOfMemory} {
+    // Convert the args to the format Clang expects.
+    const argv = try arena.alloc(?[*:0]u8, args.len + 1);
+    for (args) |arg, i| {
+        argv[i] = try arena.dupeZ(u8, arg); // TODO If there was an argsAllocZ we could avoid this allocation.
+    }
+    argv[args.len] = null;
+    const exit_code = ZigClang_main(@intCast(c_int, args.len), argv[0..args.len :null].ptr);
+    process.exit(@bitCast(u8, @truncate(i8, exit_code)));
+}
+
+const clang_args = @import("clang_options.zig").list;
+
+pub const ClangArgIterator = struct {
+    has_next: bool,
+    zig_equivalent: ZigEquivalent,
+    only_arg: []const u8,
+    second_arg: []const u8,
+    other_args: []const []const u8,
+    argv: []const []const u8,
+    next_index: usize,
+    root_args: ?*Args,
+    allocator: *Allocator,
+
+    pub const ZigEquivalent = enum {
+        target,
+        o,
+        c,
+        other,
+        positional,
+        l,
+        ignore,
+        driver_punt,
+        pic,
+        no_pic,
+        nostdlib,
+        nostdlib_cpp,
+        shared,
+        rdynamic,
+        wl,
+        pp_or_asm,
+        optimize,
+        debug,
+        sanitize,
+        linker_script,
+        verbose_cmds,
+        for_linker,
+        linker_input_z,
+        lib_dir,
+        mcpu,
+        dep_file,
+        framework_dir,
+        framework,
+        nostdlibinc,
+    };
+
+    const Args = struct {
+        next_index: usize,
+        argv: []const []const u8,
+    };
+
+    fn init(allocator: *Allocator, argv: []const []const u8) ClangArgIterator {
+        return .{
+            .next_index = 2, // `zig cc foo` this points to `foo`
+            .has_next = argv.len > 2,
+            .zig_equivalent = undefined,
+            .only_arg = undefined,
+            .second_arg = undefined,
+            .other_args = undefined,
+            .argv = argv,
+            .root_args = null,
+            .allocator = allocator,
+        };
+    }
+
+    fn next(self: *ClangArgIterator) !void {
+        assert(self.has_next);
+        assert(self.next_index < self.argv.len);
+        // In this state we know that the parameter we are looking at is a root parameter
+        // rather than an argument to a parameter.
+        // We adjust the len below when necessary.
+        self.other_args = (self.argv.ptr + self.next_index)[0..1];
+        var arg = mem.span(self.argv[self.next_index]);
+        self.incrementArgIndex();
+
+        if (mem.startsWith(u8, arg, "@")) {
+            if (self.root_args != null) return error.NestedResponseFile;
+
+            // This is a "compiler response file". We must parse the file and treat its
+            // contents as command line parameters.
+            const allocator = self.allocator;
+            const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit
+            const resp_file_path = arg[1..];
+            const resp_contents = fs.cwd().readFileAlloc(allocator, resp_file_path, max_bytes) catch |err| {
+                fatal("unable to read response file '{}': {}", .{ resp_file_path, @errorName(err) });
+            };
+            defer allocator.free(resp_contents);
+            // TODO is there a specification for this file format? Let's find it and make this parsing more robust
+            // at the very least I'm guessing this needs to handle quotes and `#` comments.
+            var it = mem.tokenize(resp_contents, " \t\r\n");
+            var resp_arg_list = std.ArrayList([]const u8).init(allocator);
+            defer resp_arg_list.deinit();
+            {
+                errdefer {
+                    for (resp_arg_list.span()) |item| {
+                        allocator.free(mem.span(item));
+                    }
+                }
+                while (it.next()) |token| {
+                    const dupe_token = try mem.dupeZ(allocator, u8, token);
+                    errdefer allocator.free(dupe_token);
+                    try resp_arg_list.append(dupe_token);
+                }
+                const args = try allocator.create(Args);
+                errdefer allocator.destroy(args);
+                args.* = .{
+                    .next_index = self.next_index,
+                    .argv = self.argv,
+                };
+                self.root_args = args;
+            }
+            const resp_arg_slice = resp_arg_list.toOwnedSlice();
+            self.next_index = 0;
+            self.argv = resp_arg_slice;
+
+            if (resp_arg_slice.len == 0) {
+                self.resolveRespFileArgs();
+                return;
+            }
+
+            self.has_next = true;
+            self.other_args = (self.argv.ptr + self.next_index)[0..1]; // We adjust len below when necessary.
+            arg = mem.span(self.argv[self.next_index]);
+            self.incrementArgIndex();
+        }
+        if (!mem.startsWith(u8, arg, "-")) {
+            self.zig_equivalent = .positional;
+            self.only_arg = arg;
+            return;
+        }
+
+        find_clang_arg: for (clang_args) |clang_arg| switch (clang_arg.syntax) {
+            .flag => {
+                const prefix_len = clang_arg.matchEql(arg);
+                if (prefix_len > 0) {
+                    self.zig_equivalent = clang_arg.zig_equivalent;
+                    self.only_arg = arg[prefix_len..];
+
+                    break :find_clang_arg;
+                }
+            },
+            .joined, .comma_joined => {
+                // joined example: --target=foo
+                // comma_joined example: -Wl,-soname,libsoundio.so.2
+                const prefix_len = clang_arg.matchStartsWith(arg);
+                if (prefix_len != 0) {
+                    self.zig_equivalent = clang_arg.zig_equivalent;
+                    self.only_arg = arg[prefix_len..]; // This will skip over the "--target=" part.
+
+                    break :find_clang_arg;
+                }
+            },
+            .joined_or_separate => {
+                // Examples: `-lfoo`, `-l foo`
+                const prefix_len = clang_arg.matchStartsWith(arg);
+                if (prefix_len == arg.len) {
+                    if (self.next_index >= self.argv.len) {
+                        fatal("Expected parameter after '{}'", .{arg});
+                    }
+                    self.only_arg = self.argv[self.next_index];
+                    self.incrementArgIndex();
+                    self.other_args.len += 1;
+                    self.zig_equivalent = clang_arg.zig_equivalent;
+
+                    break :find_clang_arg;
+                } else if (prefix_len != 0) {
+                    self.zig_equivalent = clang_arg.zig_equivalent;
+                    self.only_arg = arg[prefix_len..];
+
+                    break :find_clang_arg;
+                }
+            },
+            .joined_and_separate => {
+                // Example: `-Xopenmp-target=riscv64-linux-unknown foo`
+                const prefix_len = clang_arg.matchStartsWith(arg);
+                if (prefix_len != 0) {
+                    self.only_arg = arg[prefix_len..];
+                    if (self.next_index >= self.argv.len) {
+                        fatal("Expected parameter after '{}'", .{arg});
+                    }
+                    self.second_arg = self.argv[self.next_index];
+                    self.incrementArgIndex();
+                    self.other_args.len += 1;
+                    self.zig_equivalent = clang_arg.zig_equivalent;
+                    break :find_clang_arg;
+                }
+            },
+            .separate => if (clang_arg.matchEql(arg) > 0) {
+                if (self.next_index >= self.argv.len) {
+                    fatal("Expected parameter after '{}'", .{arg});
+                }
+                self.only_arg = self.argv[self.next_index];
+                self.incrementArgIndex();
+                self.other_args.len += 1;
+                self.zig_equivalent = clang_arg.zig_equivalent;
+                break :find_clang_arg;
+            },
+            .remaining_args_joined => {
+                const prefix_len = clang_arg.matchStartsWith(arg);
+                if (prefix_len != 0) {
+                    @panic("TODO");
+                }
+            },
+            .multi_arg => if (clang_arg.matchEql(arg) > 0) {
+                @panic("TODO");
+            },
+        }
+        else {
+            fatal("Unknown Clang option: '{}'", .{arg});
+        }
+    }
+
+    fn incrementArgIndex(self: *ClangArgIterator) void {
+        self.next_index += 1;
+        self.resolveRespFileArgs();
+    }
+
+    fn resolveRespFileArgs(self: *ClangArgIterator) void {
+        const allocator = self.allocator;
+        if (self.next_index >= self.argv.len) {
+            if (self.root_args) |root_args| {
+                self.next_index = root_args.next_index;
+                self.argv = root_args.argv;
+
+                allocator.destroy(root_args);
+                self.root_args = null;
+            }
+            if (self.next_index >= self.argv.len) {
+                self.has_next = false;
+            }
+        }
+    }
+};
+
+fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool {
+    if (ignore_case) {
+        return std.ascii.eqlIgnoreCase(a, b);
+    } else {
+        return mem.eql(u8, a, b);
+    }
+}
+
+fn is_libc_lib_name(target: std.Target, name: []const u8) bool {
+    const ignore_case = target.os.tag.isDarwin() or target.os.tag == .windows;
+
+    if (eqlIgnoreCase(ignore_case, name, "c"))
+        return true;
+
+    if (target.isMinGW()) {
+        if (eqlIgnoreCase(ignore_case, name, "m"))
+            return true;
+
+        return false;
+    }
+
+    if (target.abi.isGnu() or target.abi.isMusl() or target.os.tag.isDarwin()) {
+        if (eqlIgnoreCase(ignore_case, name, "m"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "rt"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "pthread"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "crypt"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "util"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "xnet"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "resolv"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "dl"))
+            return true;
+        if (eqlIgnoreCase(ignore_case, name, "util"))
+            return true;
+    }
+
+    if (target.os.tag.isDarwin() and eqlIgnoreCase(ignore_case, name, "System"))
+        return true;
+
+    return false;
+}
+
+fn is_libcpp_lib_name(target: std.Target, name: []const u8) bool {
+    const ignore_case = target.os.tag.isDarwin() or target.os.tag == .windows;
+
+    return eqlIgnoreCase(ignore_case, name, "c++") or
+        eqlIgnoreCase(ignore_case, name, "stdc++") or
+        eqlIgnoreCase(ignore_case, name, "c++abi");
+}
src-self-hosted/stage2.zig
@@ -414,6 +414,23 @@ export fn stage2_env(argc: c_int, argv: [*]const [*:0]const u8) c_int {
     return 0;
 }
 
+export fn stage2_cc(argc: c_int, argv: [*]const [*:0]const u8, is_cpp: bool) c_int {
+    const allocator = std.heap.c_allocator;
+
+    var args_list = argvToArrayList(allocator, argc, argv) catch |err| {
+        std.debug.print("unable to parse arguments: {}\n", .{@errorName(err)});
+        return -1;
+    };
+    defer args_list.deinit();
+
+    self_hosted_main.buildOutputType(allocator, allocator, args_list.items, if (is_cpp) .cpp else .cc) catch |err| {
+        std.debug.print("zig cc failure: {}\n", .{@errorName(err)});
+        return -1;
+    };
+
+    return 0;
+}
+
 // ABI warning
 export fn stage2_cmd_targets(
     zig_triple: ?[*:0]const u8,
@@ -1038,267 +1055,4 @@ fn convertSlice(slice: [][:0]u8, ptr: *[*][*:0]u8, len: *usize) !void {
     ptr.* = new_slice.ptr;
 }
 
-const clang_args = @import("clang_options.zig").list;
-
-// ABI warning
-pub const ClangArgIterator = extern struct {
-    has_next: bool,
-    zig_equivalent: ZigEquivalent,
-    only_arg: [*:0]const u8,
-    second_arg: [*:0]const u8,
-    other_args_ptr: [*]const [*:0]const u8,
-    other_args_len: usize,
-    argv_ptr: [*]const [*:0]const u8,
-    argv_len: usize,
-    next_index: usize,
-    root_args: ?*Args,
-
-    // ABI warning
-    pub const ZigEquivalent = extern enum {
-        target,
-        o,
-        c,
-        other,
-        positional,
-        l,
-        ignore,
-        driver_punt,
-        pic,
-        no_pic,
-        nostdlib,
-        nostdlib_cpp,
-        shared,
-        rdynamic,
-        wl,
-        pp_or_asm,
-        optimize,
-        debug,
-        sanitize,
-        linker_script,
-        verbose_cmds,
-        for_linker,
-        linker_input_z,
-        lib_dir,
-        mcpu,
-        dep_file,
-        framework_dir,
-        framework,
-        nostdlibinc,
-    };
-
-    const Args = struct {
-        next_index: usize,
-        argv_ptr: [*]const [*:0]const u8,
-        argv_len: usize,
-    };
-
-    pub fn init(argv: []const [*:0]const u8) ClangArgIterator {
-        return .{
-            .next_index = 2, // `zig cc foo` this points to `foo`
-            .has_next = argv.len > 2,
-            .zig_equivalent = undefined,
-            .only_arg = undefined,
-            .second_arg = undefined,
-            .other_args_ptr = undefined,
-            .other_args_len = undefined,
-            .argv_ptr = argv.ptr,
-            .argv_len = argv.len,
-            .root_args = null,
-        };
-    }
-
-    pub fn next(self: *ClangArgIterator) !void {
-        assert(self.has_next);
-        assert(self.next_index < self.argv_len);
-        // In this state we know that the parameter we are looking at is a root parameter
-        // rather than an argument to a parameter.
-        self.other_args_ptr = self.argv_ptr + self.next_index;
-        self.other_args_len = 1; // We adjust this value below when necessary.
-        var arg = mem.span(self.argv_ptr[self.next_index]);
-        self.incrementArgIndex();
-
-        if (mem.startsWith(u8, arg, "@")) {
-            if (self.root_args != null) return error.NestedResponseFile;
-
-            // This is a "compiler response file". We must parse the file and treat its
-            // contents as command line parameters.
-            const allocator = std.heap.c_allocator;
-            const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit
-            const resp_file_path = arg[1..];
-            const resp_contents = fs.cwd().readFileAlloc(allocator, resp_file_path, max_bytes) catch |err| {
-                std.debug.warn("unable to read response file '{}': {}\n", .{ resp_file_path, @errorName(err) });
-                process.exit(1);
-            };
-            defer allocator.free(resp_contents);
-            // TODO is there a specification for this file format? Let's find it and make this parsing more robust
-            // at the very least I'm guessing this needs to handle quotes and `#` comments.
-            var it = mem.tokenize(resp_contents, " \t\r\n");
-            var resp_arg_list = std.ArrayList([*:0]const u8).init(allocator);
-            defer resp_arg_list.deinit();
-            {
-                errdefer {
-                    for (resp_arg_list.span()) |item| {
-                        allocator.free(mem.span(item));
-                    }
-                }
-                while (it.next()) |token| {
-                    const dupe_token = try mem.dupeZ(allocator, u8, token);
-                    errdefer allocator.free(dupe_token);
-                    try resp_arg_list.append(dupe_token);
-                }
-                const args = try allocator.create(Args);
-                errdefer allocator.destroy(args);
-                args.* = .{
-                    .next_index = self.next_index,
-                    .argv_ptr = self.argv_ptr,
-                    .argv_len = self.argv_len,
-                };
-                self.root_args = args;
-            }
-            const resp_arg_slice = resp_arg_list.toOwnedSlice();
-            self.next_index = 0;
-            self.argv_ptr = resp_arg_slice.ptr;
-            self.argv_len = resp_arg_slice.len;
-
-            if (resp_arg_slice.len == 0) {
-                self.resolveRespFileArgs();
-                return;
-            }
-
-            self.has_next = true;
-            self.other_args_ptr = self.argv_ptr + self.next_index;
-            self.other_args_len = 1; // We adjust this value below when necessary.
-            arg = mem.span(self.argv_ptr[self.next_index]);
-            self.incrementArgIndex();
-        }
-        if (!mem.startsWith(u8, arg, "-")) {
-            self.zig_equivalent = .positional;
-            self.only_arg = arg.ptr;
-            return;
-        }
-
-        find_clang_arg: for (clang_args) |clang_arg| switch (clang_arg.syntax) {
-            .flag => {
-                const prefix_len = clang_arg.matchEql(arg);
-                if (prefix_len > 0) {
-                    self.zig_equivalent = clang_arg.zig_equivalent;
-                    self.only_arg = arg.ptr + prefix_len;
-
-                    break :find_clang_arg;
-                }
-            },
-            .joined, .comma_joined => {
-                // joined example: --target=foo
-                // comma_joined example: -Wl,-soname,libsoundio.so.2
-                const prefix_len = clang_arg.matchStartsWith(arg);
-                if (prefix_len != 0) {
-                    self.zig_equivalent = clang_arg.zig_equivalent;
-                    self.only_arg = arg.ptr + prefix_len; // This will skip over the "--target=" part.
-
-                    break :find_clang_arg;
-                }
-            },
-            .joined_or_separate => {
-                // Examples: `-lfoo`, `-l foo`
-                const prefix_len = clang_arg.matchStartsWith(arg);
-                if (prefix_len == arg.len) {
-                    if (self.next_index >= self.argv_len) {
-                        std.debug.warn("Expected parameter after '{}'\n", .{arg});
-                        process.exit(1);
-                    }
-                    self.only_arg = self.argv_ptr[self.next_index];
-                    self.incrementArgIndex();
-                    self.other_args_len += 1;
-                    self.zig_equivalent = clang_arg.zig_equivalent;
-
-                    break :find_clang_arg;
-                } else if (prefix_len != 0) {
-                    self.zig_equivalent = clang_arg.zig_equivalent;
-                    self.only_arg = arg.ptr + prefix_len;
-
-                    break :find_clang_arg;
-                }
-            },
-            .joined_and_separate => {
-                // Example: `-Xopenmp-target=riscv64-linux-unknown foo`
-                const prefix_len = clang_arg.matchStartsWith(arg);
-                if (prefix_len != 0) {
-                    self.only_arg = arg.ptr + prefix_len;
-                    if (self.next_index >= self.argv_len) {
-                        std.debug.warn("Expected parameter after '{}'\n", .{arg});
-                        process.exit(1);
-                    }
-                    self.second_arg = self.argv_ptr[self.next_index];
-                    self.incrementArgIndex();
-                    self.other_args_len += 1;
-                    self.zig_equivalent = clang_arg.zig_equivalent;
-                    break :find_clang_arg;
-                }
-            },
-            .separate => if (clang_arg.matchEql(arg) > 0) {
-                if (self.next_index >= self.argv_len) {
-                    std.debug.warn("Expected parameter after '{}'\n", .{arg});
-                    process.exit(1);
-                }
-                self.only_arg = self.argv_ptr[self.next_index];
-                self.incrementArgIndex();
-                self.other_args_len += 1;
-                self.zig_equivalent = clang_arg.zig_equivalent;
-                break :find_clang_arg;
-            },
-            .remaining_args_joined => {
-                const prefix_len = clang_arg.matchStartsWith(arg);
-                if (prefix_len != 0) {
-                    @panic("TODO");
-                }
-            },
-            .multi_arg => if (clang_arg.matchEql(arg) > 0) {
-                @panic("TODO");
-            },
-        }
-        else {
-            std.debug.warn("Unknown Clang option: '{}'\n", .{arg});
-            process.exit(1);
-        }
-    }
-
-    fn incrementArgIndex(self: *ClangArgIterator) void {
-        self.next_index += 1;
-        self.resolveRespFileArgs();
-    }
-
-    fn resolveRespFileArgs(self: *ClangArgIterator) void {
-        const allocator = std.heap.c_allocator;
-        if (self.next_index >= self.argv_len) {
-            if (self.root_args) |root_args| {
-                self.next_index = root_args.next_index;
-                self.argv_ptr = root_args.argv_ptr;
-                self.argv_len = root_args.argv_len;
-
-                allocator.destroy(root_args);
-                self.root_args = null;
-            }
-            if (self.next_index >= self.argv_len) {
-                self.has_next = false;
-            }
-        }
-    }
-};
-
-export fn stage2_clang_arg_iterator(
-    result: *ClangArgIterator,
-    argc: usize,
-    argv: [*]const [*:0]const u8,
-) void {
-    result.* = ClangArgIterator.init(argv[0..argc]);
-}
-
-export fn stage2_clang_arg_next(it: *ClangArgIterator) Error {
-    it.next() catch |err| switch (err) {
-        error.NestedResponseFile => return .NestedResponseFile,
-        error.OutOfMemory => return .OutOfMemory,
-    };
-    return .None;
-}
-
 export const stage2_is_zig0 = false;
build.zig
@@ -9,6 +9,7 @@ const ArrayList = std.ArrayList;
 const io = std.io;
 const fs = std.fs;
 const InstallDirectoryOptions = std.build.InstallDirectoryOptions;
+const assert = std.debug.assert;
 
 const zig_version = std.builtin.Version{ .major = 0, .minor = 6, .patch = 0 };
 
@@ -57,11 +58,13 @@ pub fn build(b: *Builder) !void {
 
     if (!only_install_lib_files) {
         var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
+        exe.install();
         exe.setBuildMode(mode);
         exe.setTarget(target);
         test_step.dependOn(&exe.step);
         b.default_step.dependOn(&exe.step);
 
+        exe.addBuildOption(bool, "have_llvm", enable_llvm);
         if (enable_llvm) {
             const config_h_text = if (config_h_path_option) |config_h_path|
                 try std.fs.cwd().readFileAlloc(b.allocator, toNativePathSep(b, config_h_path), max_config_h_bytes)
@@ -73,11 +76,8 @@ pub fn build(b: *Builder) !void {
 
             try configureStage2(b, exe, ctx);
         }
-        if (!only_install_lib_files) {
-            exe.install();
-        }
         const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source");
-        const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false;
+        const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse enable_llvm;
         if (link_libc) {
             exe.linkLibC();
             test_stage2.linkLibC();
@@ -323,17 +323,13 @@ fn configureStage2(b: *Builder, exe: anytype, ctx: Context) !void {
     exe.addIncludeDir("src");
     exe.addIncludeDir(ctx.cmake_binary_dir);
     addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp");
-    if (ctx.lld_include_dir.len != 0) {
-        exe.addIncludeDir(ctx.lld_include_dir);
+    assert(ctx.lld_include_dir.len != 0);
+    exe.addIncludeDir(ctx.lld_include_dir);
+    {
         var it = mem.tokenize(ctx.lld_libraries, ";");
         while (it.next()) |lib| {
             exe.addObjectFile(lib);
         }
-    } else {
-        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_wasm");
-        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_elf");
-        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_coff");
-        addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_lib");
     }
     {
         var it = mem.tokenize(ctx.clang_libraries, ";");
@@ -343,42 +339,51 @@ fn configureStage2(b: *Builder, exe: anytype, ctx: Context) !void {
     }
     dependOnLib(b, exe, ctx.llvm);
 
-    if (exe.target.getOsTag() == .linux) {
-        // First we try to static link against gcc libstdc++. If that doesn't work,
-        // we fall back to -lc++ and cross our fingers.
-        addCxxKnownPath(b, ctx, exe, "libstdc++.a", "") catch |err| switch (err) {
-            error.RequiredLibraryNotFound => {
-                exe.linkSystemLibrary("c++");
-            },
-            else => |e| return e,
-        };
+    // Boy, it sure would be nice to simply linkSystemLibrary("c++") and rely on zig's
+    // ability to provide libc++ right? Well thanks to C++ not having a stable ABI this
+    // will cause linker errors. It would work in the situation when `zig cc` is used to
+    // build LLVM, Clang, and LLD, however when depending on them as system libraries, system
+    // libc++ must be used.
+    const cross_compile = false; // TODO
+    if (cross_compile) {
+        // In this case we assume that zig cc was used to build the LLVM, Clang, LLD dependencies.
+        exe.linkSystemLibrary("c++");
+    } else {
+        if (exe.target.getOsTag() == .linux) {
+            // First we try to static link against gcc libstdc++. If that doesn't work,
+            // we fall back to -lc++ and cross our fingers.
+            addCxxKnownPath(b, ctx, exe, "libstdc++.a", "") catch |err| switch (err) {
+                error.RequiredLibraryNotFound => {
+                    exe.linkSystemLibrary("c++");
+                },
+                else => |e| return e,
+            };
 
-        exe.linkSystemLibrary("pthread");
-    } else if (exe.target.isFreeBSD()) {
-        try addCxxKnownPath(b, ctx, exe, "libc++.a", null);
-        exe.linkSystemLibrary("pthread");
-    } else if (exe.target.isDarwin()) {
-        if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "")) {
-            // Compiler is GCC.
-            try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null);
             exe.linkSystemLibrary("pthread");
-            // TODO LLD cannot perform this link.
-            // See https://github.com/ziglang/zig/issues/1535
-            exe.enableSystemLinkerHack();
-        } else |err| switch (err) {
-            error.RequiredLibraryNotFound => {
-                // System compiler, not gcc.
-                exe.linkSystemLibrary("c++");
-            },
-            else => |e| return e,
+        } else if (exe.target.isFreeBSD()) {
+            try addCxxKnownPath(b, ctx, exe, "libc++.a", null);
+            exe.linkSystemLibrary("pthread");
+        } else if (exe.target.isDarwin()) {
+            if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "")) {
+                // Compiler is GCC.
+                try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null);
+                exe.linkSystemLibrary("pthread");
+                // TODO LLD cannot perform this link.
+                // See https://github.com/ziglang/zig/issues/1535
+                exe.enableSystemLinkerHack();
+            } else |err| switch (err) {
+                error.RequiredLibraryNotFound => {
+                    // System compiler, not gcc.
+                    exe.linkSystemLibrary("c++");
+                },
+                else => |e| return e,
+            }
         }
-    }
 
-    if (ctx.dia_guids_lib.len != 0) {
-        exe.addObjectFile(ctx.dia_guids_lib);
+        if (ctx.dia_guids_lib.len != 0) {
+            exe.addObjectFile(ctx.dia_guids_lib);
+        }
     }
-
-    exe.linkSystemLibrary("c");
 }
 
 fn addCxxKnownPath(