Commit 03a23418ff

Andrew Kelley <andrew@ziglang.org>
2020-09-12 09:51:06
stage2: linking with LLD and building glibc static CRT files
* implement --debug-cc and --debug-link * implement C source files having extra flags - TODO a way to pass them on the CLI * introduce the Directory abstraction which contains both an open file descriptor and a file path name. The former is preferred but the latter is needed when communicating paths over a command line (e.g. to Clang or LLD). * use the cache hash to choose an artifact directory - TODO: use separate cache hash instances for the zig module and each C object * Module: introduce the crt_files table for keeping track of built libc artifacts for linking. * Add the ability to build 4/6 of the glibc static CRT lib files. * The zig-cache directory is now passed as a parameter to Module. * Implement the CLI logic of -femit-bin and -femit-h - TODO: respect -fno-emit-bin - TODO: the emit .h feature * Add the -fvalgrind, -fstack-check, and --single-threaded CLI options. * Implement the logic for auto detecting whether to enable PIC, sanitize-C, stack-check, valgrind, and single-threaded. * Properly add PIC args (or not) to clang argv. * Implement renaming clang-compiled object files into their proper place within the cache artifact directory. - TODO: std lib needs a proper higher level abstraction for std.os.renameat. * Package is cleaned up to use the "Unmanaged" StringHashMap and use the new Directory abstraction. * Clean up zig lib directory detection to make proper use of directory handles. * Linker code invokes LLD. - TODO properly deal with the stdout and stderr that we get from it and expose diagnostics from the Module API that match the expected error message format. * Delete the bitrotted LLVM C ABI bindings. We'll resurrect just the functions we need as we introduce dependencies on them. So far it only has ZigLLDLink in it. * Remove dead timer code. * `zig env` now prints the path to the zig executable as well.
1 parent 8374be1
src-self-hosted/link/C.zig
@@ -28,7 +28,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVMHasNoCBackend;
     if (options.use_lld) return error.LLDHasNoCBackend;
 
-    const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
+    const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
     errdefer file.close();
 
     var c_file = try allocator.create(C);
src-self-hosted/link/Coff.zig
@@ -116,7 +116,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO
 
-    const file = try options.dir.createFile(sub_path, .{
+    const file = try options.directory.handle.createFile(sub_path, .{
         .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
src-self-hosted/link/Elf.zig
@@ -19,6 +19,7 @@ const File = link.File;
 const Elf = @This();
 const build_options = @import("build_options");
 const target_util = @import("../target.zig");
+const fatal = @import("main.zig").fatal;
 
 const default_entry_addr = 0x8000000;
 
@@ -222,7 +223,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
 
     if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO
 
-    const file = try options.dir.createFile(sub_path, .{
+    const file = try options.directory.handle.createFile(sub_path, .{
         .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
@@ -844,7 +845,7 @@ fn flushInner(self: *Elf, module: *Module) !void {
         }
         // Write the form for the compile unit, which must match the abbrev table above.
         const name_strp = try self.makeDebugString(self.base.options.root_pkg.?.root_src_path);
-        const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.?.root_src_dir_path);
+        const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.?.root_src_directory.path.?);
         const producer_strp = try self.makeDebugString(link.producer_string);
         // Currently only one compilation unit is supported, so the address range is simply
         // identical to the main program header virtual address and memory size.
@@ -1199,11 +1200,6 @@ fn flushInner(self: *Elf, module: *Module) !void {
 }
 
 fn linkWithLLD(self: *Elf, module: *Module) !void {
-    // If there is no Zig code to compile, then we should skip flushing the output file because it
-    // will not be part of the linker line anyway.
-    if (module.root_pkg != null) {
-        try self.flushInner(module);
-    }
     var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
     defer arena_allocator.deinit();
     const arena = &arena_allocator.allocator;
@@ -1292,7 +1288,7 @@ fn linkWithLLD(self: *Elf, module: *Module) !void {
         try argv.append("-pie");
     }
 
-    const full_out_path = if (self.base.options.dir_path) |dir_path|
+    const full_out_path = if (self.base.options.directory.path) |dir_path|
         try std.fs.path.join(arena, &[_][]const u8{dir_path, self.base.options.sub_path})
     else 
         self.base.options.sub_path;
@@ -1382,6 +1378,30 @@ fn linkWithLLD(self: *Elf, module: *Module) !void {
     // Positional arguments to the linker such as object files.
     try argv.appendSlice(self.base.options.objects);
 
+    for (module.c_object_table.items()) |entry| {
+        const c_object = entry.key;
+        switch (c_object.status) {
+            .new => unreachable,
+            .failure => return error.NotAllCSourceFilesAvailableToLink,
+            .success => |full_obj_path| {
+                try argv.append(full_obj_path);
+            },
+        }
+    }
+
+    // If there is no Zig code to compile, then we should skip flushing the output file because it
+    // will not be part of the linker line anyway.
+    if (module.root_pkg != null) {
+        try self.flushInner(module);
+
+        const obj_basename = self.base.intermediary_basename.?;
+        const full_obj_path = if (self.base.options.directory.path) |dir_path|
+            try std.fs.path.join(arena, &[_][]const u8{dir_path, obj_basename})
+        else 
+            obj_basename;
+        try argv.append(full_obj_path);
+    }
+
     // TODO compiler-rt and libc
     //if (!g->is_dummy_so && (g->out_type == OutTypeExe || is_dyn_lib)) {
     //    if (g->libc_link_lib == nullptr) {
@@ -1461,10 +1481,31 @@ fn linkWithLLD(self: *Elf, module: *Module) !void {
         try argv.append("-Bsymbolic");
     }
 
-    for (argv.items) |arg| {
-        std.debug.print("{} ", .{arg});
+    if (self.base.options.debug_link) {
+        for (argv.items[0 .. argv.items.len - 1]) |arg| {
+            std.debug.print("{} ", .{arg});
+        }
+        std.debug.print("{}\n", .{argv.items[argv.items.len - 1]});
     }
-    @panic("invoke LLD");
+
+    // Oh, snapplesauce! We need null terminated argv.
+    // TODO allocSentinel crashed stage1 so this is working around it.
+    const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1);
+    new_argv_with_sentinel[argv.items.len] = null;
+    const new_argv = new_argv_with_sentinel[0..argv.items.len: null];
+    for (argv.items) |arg, i| {
+        new_argv[i] = try arena.dupeZ(u8, arg);
+    }
+
+    const ZigLLDLink = @import("../llvm.zig").ZigLLDLink;
+    const ok = ZigLLDLink(.ELF, new_argv.ptr, new_argv.len, append_diagnostic, 0, 0);
+    if (!ok) return error.LLDReportedFailure;
+}
+
+fn append_diagnostic(context: usize, ptr: [*]const u8, len: usize) callconv(.C) void {
+    // TODO collect diagnostics and handle cleanly
+    const msg = ptr[0..len];
+    std.log.err("LLD: {}", .{msg});
 }
 
 fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void {
@@ -2681,7 +2722,7 @@ fn dbgLineNeededHeaderBytes(self: Elf) u32 {
         directory_count * 8 + file_name_count * 8 +
     // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like
     // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly.
-        self.base.options.root_pkg.?.root_src_dir_path.len +
+        self.base.options.root_pkg.?.root_src_directory.path.?.len +
         self.base.options.root_pkg.?.root_src_path.len);
 }
 
src-self-hosted/link/MachO.zig
@@ -140,7 +140,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO
 
-    const file = try options.dir.createFile(sub_path, .{
+    const file = try options.directory.handle.createFile(sub_path, .{
         .truncate = false,
         .read = true,
         .mode = link.determineMode(options),
src-self-hosted/link/Wasm.zig
@@ -56,7 +56,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
 
     // TODO: read the file and keep vaild parts instead of truncating
-    const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true });
+    const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
     errdefer file.close();
 
     const wasm = try allocator.create(Wasm);
src-self-hosted/glibc.zig
@@ -2,6 +2,9 @@ const std = @import("std");
 const Allocator = std.mem.Allocator;
 const target_util = @import("target.zig");
 const mem = std.mem;
+const Module = @import("Module.zig");
+const path = std.fs.path;
+const build_options = @import("build_options");
 
 pub const Lib = struct {
     name: []const u8,
@@ -60,7 +63,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError!
     var version_table = std.AutoHashMapUnmanaged(target_util.ArchOsAbi, [*]VerList){};
     errdefer version_table.deinit(gpa);
 
-    var glibc_dir = zig_lib_dir.openDir("libc" ++ std.fs.path.sep_str ++ "glibc", .{}) catch |err| {
+    var glibc_dir = zig_lib_dir.openDir("libc" ++ path.sep_str ++ "glibc", .{}) catch |err| {
         std.log.err("unable to open glibc dir: {}", .{@errorName(err)});
         return error.ZigInstallationCorrupt;
     };
@@ -229,3 +232,394 @@ fn findLib(name: []const u8) ?*const Lib {
     }
     return null;
 }
+
+pub const CRTFile = enum {
+    crti_o,
+    crtn_o,
+    start_os,
+    abi_note_o,
+    scrt1_o,
+    libc_nonshared_a,
+};
+
+pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void {
+    if (!build_options.have_llvm) {
+        return error.ZigCompilerNotBuiltWithLLVMExtensions;
+    }
+    const gpa = mod.gpa;
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    errdefer arena_allocator.deinit();
+    const arena = &arena_allocator.allocator;
+
+    switch (crt_file) {
+        .crti_o => {
+            var args = std.ArrayList([]const u8).init(arena);
+            try add_include_dirs(mod, arena, &args);
+            try args.appendSlice(&[_][]const u8{
+                "-D_LIBC_REENTRANT",
+                "-include",
+                try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"),
+                "-DMODULE_NAME=libc",
+                "-Wno-nonportable-include-path",
+                "-include",
+                try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"),
+                "-DTOP_NAMESPACE=glibc",
+                "-DASSEMBLER",
+                "-g",
+                "-Wa,--noexecstack",
+            });
+            const c_source_file: Module.CSourceFile = .{
+                .src_path = try start_asm_path(mod, arena, "crti.S"),
+                .extra_flags = args.items,
+            };
+            return build_libc_object(mod, "crti.o", c_source_file);
+        },
+        .crtn_o => {
+            var args = std.ArrayList([]const u8).init(arena);
+            try add_include_dirs(mod, arena, &args);
+            try args.appendSlice(&[_][]const u8{
+                "-D_LIBC_REENTRANT",
+                "-DMODULE_NAME=libc",
+                "-DTOP_NAMESPACE=glibc",
+                "-DASSEMBLER",
+                "-g",
+                "-Wa,--noexecstack",
+            });
+            const c_source_file: Module.CSourceFile = .{
+                .src_path = try start_asm_path(mod, arena, "crtn.S"),
+                .extra_flags = args.items,
+            };
+            return build_libc_object(mod, "crtn.o", c_source_file);
+        },
+        .start_os => {
+            var args = std.ArrayList([]const u8).init(arena);
+            try add_include_dirs(mod, arena, &args);
+            try args.appendSlice(&[_][]const u8{
+                "-D_LIBC_REENTRANT",
+                "-include",
+                try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"),
+                "-DMODULE_NAME=libc",
+                "-Wno-nonportable-include-path",
+                "-include",
+                try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"),
+                "-DPIC",
+                "-DSHARED",
+                "-DTOP_NAMESPACE=glibc",
+                "-DASSEMBLER",
+                "-g",
+                "-Wa,--noexecstack",
+            });
+            const c_source_file: Module.CSourceFile = .{
+                .src_path = try start_asm_path(mod, arena, "start.S"),
+                .extra_flags = args.items,
+            };
+            return build_libc_object(mod, "start.os", c_source_file);
+        },
+        .abi_note_o => {
+            var args = std.ArrayList([]const u8).init(arena);
+            try args.appendSlice(&[_][]const u8{
+                "-I",
+                try lib_path(mod, arena, lib_libc_glibc ++ "glibc" ++ path.sep_str ++ "csu"),
+            });
+            try add_include_dirs(mod, arena, &args);
+            try args.appendSlice(&[_][]const u8{
+                "-D_LIBC_REENTRANT",
+                "-DMODULE_NAME=libc",
+                "-DTOP_NAMESPACE=glibc",
+                "-DASSEMBLER",
+                "-g",
+                "-Wa,--noexecstack",
+            });
+            const c_source_file: Module.CSourceFile = .{
+                .src_path = try lib_path(mod, arena, lib_libc_glibc ++ "csu" ++ path.sep_str ++ "abi-note.S"),
+                .extra_flags = args.items,
+            };
+            return build_libc_object(mod, "abi-note.o", c_source_file);
+        },
+        .scrt1_o => {
+            return error.Unimplemented; // TODO
+        },
+        .libc_nonshared_a => {
+            return error.Unimplemented; // TODO
+        },
+    }
+}
+
+fn start_asm_path(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 {
+    const arch = mod.getTarget().cpu.arch;
+    const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le;
+    const is_aarch64 = arch == .aarch64 or arch == .aarch64_be;
+    const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9;
+    const is_64 = arch.ptrBitWidth() == 64;
+
+    const s = path.sep_str;
+
+    var result = std.ArrayList(u8).init(arena);
+    try result.appendSlice(mod.zig_lib_directory.path.?);
+    try result.appendSlice(s ++ "libc" ++ s ++ "glibc" ++ s ++ "sysdeps" ++ s);
+    if (is_sparc) {
+        if (is_64) {
+            try result.appendSlice("sparc" ++ s ++ "sparc64");
+        } else {
+            try result.appendSlice("sparc" ++ s ++ "sparc32");
+        }
+    } else if (arch.isARM()) {
+        try result.appendSlice("arm");
+    } else if (arch.isMIPS()) {
+        try result.appendSlice("mips");
+    } else if (arch == .x86_64) {
+        try result.appendSlice("x86_64");
+    } else if (arch == .i386) {
+        try result.appendSlice("i386");
+    } else if (is_aarch64) {
+        try result.appendSlice("aarch64");
+    } else if (arch.isRISCV()) {
+        try result.appendSlice("riscv");
+    } else if (is_ppc) {
+        if (is_64) {
+            try result.appendSlice("powerpc" ++ s ++ "powerpc64");
+        } else {
+            try result.appendSlice("powerpc" ++ s ++ "powerpc32");
+        }
+    }
+
+    try result.appendSlice(s);
+    try result.appendSlice(basename);
+    return result.items;
+}
+
+fn add_include_dirs(mod: *Module, arena: *Allocator, args: *std.ArrayList([]const u8)) error{OutOfMemory}!void {
+    const target = mod.getTarget();
+    const arch = target.cpu.arch;
+    const opt_nptl: ?[]const u8 = if (target.os.tag == .linux) "nptl" else "htl";
+    const glibc = try lib_path(mod, arena, lib_libc ++ "glibc");
+
+    const s = path.sep_str;
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "include"));
+
+    if (target.os.tag == .linux) {
+        try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux"));
+    }
+
+    if (opt_nptl) |nptl| {
+        try add_include_dirs_arch(arena, args, arch, nptl, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps"));
+    }
+
+    if (target.os.tag == .linux) {
+        try args.append("-I");
+        try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++
+            "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "generic"));
+
+        try args.append("-I");
+        try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++
+            "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "include"));
+        try args.append("-I");
+        try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++
+            "unix" ++ s ++ "sysv" ++ s ++ "linux"));
+    }
+    if (opt_nptl) |nptl| {
+        try args.append("-I");
+        try args.append(try path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, lib_libc_glibc ++ "sysdeps", nptl }));
+    }
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "pthread"));
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv"));
+
+    try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix"));
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix"));
+
+    try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps"));
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "generic"));
+
+    try args.append("-I");
+    try args.append(try path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, lib_libc ++ "glibc" }));
+
+    try args.append("-I");
+    try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}", .{
+        mod.zig_lib_directory.path.?, @tagName(arch), @tagName(target.os.tag), @tagName(target.abi),
+    }));
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc ++ "include" ++ s ++ "generic-glibc"));
+
+    try args.append("-I");
+    try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-linux-any", .{
+        mod.zig_lib_directory.path.?, @tagName(arch),
+    }));
+
+    try args.append("-I");
+    try args.append(try lib_path(mod, arena, lib_libc ++ "include" ++ s ++ "any-linux-any"));
+}
+
+fn add_include_dirs_arch(
+    arena: *Allocator,
+    args: *std.ArrayList([]const u8),
+    arch: std.Target.Cpu.Arch,
+    opt_nptl: ?[]const u8,
+    dir: []const u8,
+) error{OutOfMemory}!void {
+    const is_x86 = arch == .i386 or arch == .x86_64;
+    const is_aarch64 = arch == .aarch64 or arch == .aarch64_be;
+    const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le;
+    const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9;
+    const is_64 = arch.ptrBitWidth() == 64;
+
+    const s = path.sep_str;
+
+    if (is_x86) {
+        if (arch == .x86_64) {
+            if (opt_nptl) |nptl| {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "x86_64", nptl }));
+            } else {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "x86_64" }));
+            }
+        } else if (arch == .i386) {
+            if (opt_nptl) |nptl| {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "i386", nptl }));
+            } else {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "i386" }));
+            }
+        }
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "x86", nptl }));
+        } else {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "x86" }));
+        }
+    } else if (arch.isARM()) {
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "arm", nptl }));
+        } else {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "arm" }));
+        }
+    } else if (arch.isMIPS()) {
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "mips", nptl }));
+        } else {
+            if (is_64) {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" ++ s ++ "mips64" }));
+            } else {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" ++ s ++ "mips32" }));
+            }
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "mips" }));
+        }
+    } else if (is_sparc) {
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc", nptl }));
+        } else {
+            if (is_64) {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" ++ s ++ "sparc64" }));
+            } else {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" ++ s ++ "sparc32" }));
+            }
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "sparc" }));
+        }
+    } else if (is_aarch64) {
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "aarch64", nptl }));
+        } else {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "aarch64" }));
+        }
+    } else if (is_ppc) {
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc", nptl }));
+        } else {
+            if (is_64) {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" ++ s ++ "powerpc64" }));
+            } else {
+                try args.append("-I");
+                try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" ++ s ++ "powerpc32" }));
+            }
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "powerpc" }));
+        }
+    } else if (arch.isRISCV()) {
+        if (opt_nptl) |nptl| {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "riscv", nptl }));
+        } else {
+            try args.append("-I");
+            try args.append(try path.join(arena, &[_][]const u8{ dir, "riscv" }));
+        }
+    }
+}
+
+fn path_from_lib(mod: *Module, arena: *Allocator, sub_path: []const u8) ![]const u8 {
+    return path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, sub_path });
+}
+
+const lib_libc = "libc" ++ path.sep_str;
+const lib_libc_glibc = lib_libc ++ "glibc" ++ path.sep_str;
+
+fn lib_path(mod: *Module, arena: *Allocator, sub_path: []const u8) ![]const u8 {
+    return path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, sub_path });
+}
+
+fn build_libc_object(mod: *Module, basename: []const u8, c_source_file: Module.CSourceFile) !void {
+    // TODO: This is extracted into a local variable to work around a stage1 miscompilation.
+    const emit_bin = Module.EmitLoc{
+        .directory = null, // Put it in the cache directory.
+        .basename = basename,
+    };
+    const sub_module = try Module.create(mod.gpa, .{
+        // TODO use the global cache directory here
+        .zig_cache_directory = mod.zig_cache_directory,
+        .zig_lib_directory = mod.zig_lib_directory,
+        .target = mod.getTarget(),
+        .root_name = mem.split(basename, ".").next().?,
+        .root_pkg = null,
+        .output_mode = .Obj,
+        .rand = mod.rand,
+        .libc_installation = mod.bin_file.options.libc_installation,
+        .emit_bin = emit_bin,
+        .optimize_mode = mod.bin_file.options.optimize_mode,
+        .want_sanitize_c = false,
+        .want_stack_check = false,
+        .want_valgrind = false,
+        .want_pic = mod.bin_file.options.pic,
+        .emit_h = null,
+        .strip = mod.bin_file.options.strip,
+        .is_native_os = mod.bin_file.options.is_native_os,
+        .self_exe_path = mod.self_exe_path,
+        .c_source_files = &[1]Module.CSourceFile{c_source_file},
+        .debug_cc = mod.debug_cc,
+        .debug_link = mod.bin_file.options.debug_link,
+    });
+    defer sub_module.destroy();
+
+    try sub_module.update();
+
+    try mod.crt_files.ensureCapacity(mod.gpa, mod.crt_files.count() + 1);
+    const artifact_path = try std.fs.path.join(mod.gpa, &[_][]const u8{
+        sub_module.zig_cache_artifact_directory.path.?, basename,
+    });
+    mod.crt_files.putAssumeCapacityNoClobber(basename, artifact_path);
+}
src-self-hosted/introspect.zig
@@ -1,77 +1,65 @@
-//! Introspection and determination of system libraries needed by zig.
-
 const std = @import("std");
 const mem = std.mem;
 const fs = std.fs;
 const CacheHash = std.cache_hash.CacheHash;
-
-/// Caller must free result
-pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 {
-    {
-        const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib", "zig" });
-        errdefer allocator.free(test_zig_dir);
-
-        const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });
-        defer allocator.free(test_index_file);
-
-        if (fs.cwd().openFile(test_index_file, .{})) |file| {
-            file.close();
-            return test_zig_dir;
-        } else |err| switch (err) {
-            error.FileNotFound => {
-                allocator.free(test_zig_dir);
-            },
-            else => |e| return e,
-        }
+const Module = @import("Module.zig");
+
+/// Returns the sub_path that worked, or `null` if none did.
+/// The path of the returned Directory is relative to `base`.
+/// The handle of the returned Directory is open.
+fn testZigInstallPrefix(base_dir: fs.Dir) ?Module.Directory {
+    const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig";
+
+    zig_dir: {
+        // Try lib/zig/std/std.zig
+        const lib_zig = "lib" ++ fs.path.sep_str ++ "zig";
+        var test_zig_dir = base_dir.openDir(lib_zig, .{}) catch break :zig_dir;
+        const file = test_zig_dir.openFile(test_index_file, .{}) catch {
+            test_zig_dir.close();
+            break :zig_dir;
+        };
+        file.close();
+        return Module.Directory{ .handle = test_zig_dir, .path = lib_zig };
     }
 
-    // Also try without "zig"
-    const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib" });
-    errdefer allocator.free(test_zig_dir);
-
-    const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });
-    defer allocator.free(test_index_file);
-
-    const file = try fs.cwd().openFile(test_index_file, .{});
+    // Try lib/std/std.zig
+    var test_zig_dir = base_dir.openDir("lib", .{}) catch return null;
+    const file = test_zig_dir.openFile(test_index_file, .{}) catch {
+        test_zig_dir.close();
+        return null;
+    };
     file.close();
-
-    return test_zig_dir;
+    return Module.Directory{ .handle = test_zig_dir, .path = "lib" };
 }
 
-/// Caller must free result
-pub fn findZigLibDir(allocator: *mem.Allocator) ![]u8 {
-    const self_exe_path = try fs.selfExePathAlloc(allocator);
-    defer allocator.free(self_exe_path);
+/// Both the directory handle and the path are newly allocated resources which the caller now owns.
+pub fn findZigLibDir(gpa: *mem.Allocator) !Module.Directory {
+    const self_exe_path = try fs.selfExePathAlloc(gpa);
+    defer gpa.free(self_exe_path);
 
-    var cur_path: []const u8 = self_exe_path;
-    while (true) {
-        const test_dir = fs.path.dirname(cur_path) orelse ".";
-
-        if (mem.eql(u8, test_dir, cur_path)) {
-            break;
-        }
+    return findZigLibDirFromSelfExe(gpa, self_exe_path);
+}
 
-        return testZigInstallPrefix(allocator, test_dir) catch |err| {
-            cur_path = test_dir;
-            continue;
+/// Both the directory handle and the path are newly allocated resources which the caller now owns.
+pub fn findZigLibDirFromSelfExe(
+    allocator: *mem.Allocator,
+    self_exe_path: []const u8,
+) error{ OutOfMemory, FileNotFound }!Module.Directory {
+    const cwd = fs.cwd();
+    var cur_path: []const u8 = self_exe_path;
+    while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) {
+        var base_dir = cwd.openDir(dirname, .{}) catch continue;
+        defer base_dir.close();
+
+        const sub_directory = testZigInstallPrefix(base_dir) orelse continue;
+        return Module.Directory{
+            .handle = sub_directory.handle,
+            .path = try fs.path.join(allocator, &[_][]const u8{ dirname, sub_directory.path.? }),
         };
     }
-
     return error.FileNotFound;
 }
 
-pub fn resolveZigLibDir(allocator: *mem.Allocator) ![]u8 {
-    return findZigLibDir(allocator) catch |err| {
-        std.debug.print(
-            \\Unable to find zig lib directory: {}.
-            \\Reinstall Zig or use --zig-install-prefix.
-            \\
-        , .{@errorName(err)});
-
-        return error.ZigLibDirNotFound;
-    };
-}
-
 /// Caller owns returned memory.
 pub fn resolveGlobalCacheDir(allocator: *mem.Allocator) ![]u8 {
     const appname = "zig";
src-self-hosted/link.zig
@@ -11,11 +11,9 @@ const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
 
 pub const Options = struct {
-    dir: fs.Dir,
-    /// Redundant with dir. Needed when linking with LLD because we have to pass paths rather
-    /// than file descriptors. `null` means cwd. OK to pass `null` when `use_lld` is `false`.
-    dir_path: ?[]const u8,
-    /// Path to the output file, relative to dir.
+    /// Where the output will go.
+    directory: Module.Directory,
+    /// Path to the output file, relative to `directory`.
     sub_path: []const u8,
     target: std.Target,
     output_mode: std.builtin.OutputMode,
@@ -53,6 +51,11 @@ pub const Options = struct {
     z_defs: bool = false,
     bind_global_refs_locally: bool,
     is_native_os: bool,
+    pic: bool,
+    valgrind: bool,
+    stack_check: bool,
+    single_threaded: bool,
+    debug_link: bool = false,
     gc_sections: ?bool = null,
     allow_shlib_undefined: ?bool = null,
     linker_script: ?[]const u8 = null,
@@ -154,7 +157,7 @@ pub const File = struct {
         switch (base.tag) {
             .coff, .elf, .macho => {
                 if (base.file != null) return;
-                base.file = try base.options.dir.createFile(base.options.sub_path, .{
+                base.file = try base.options.directory.handle.createFile(base.options.sub_path, .{
                     .truncate = false,
                     .read = true,
                     .mode = determineMode(base.options),
src-self-hosted/llvm.zig
@@ -1,293 +1,20 @@
-const c = @import("c.zig");
-const assert = @import("std").debug.assert;
-
-// we wrap the c module for 3 reasons:
-// 1. to avoid accidentally calling the non-thread-safe functions
-// 2. patch up some of the types to remove nullability
-// 3. some functions have been augmented by zig_llvm.cpp to be more powerful,
-//    such as ZigLLVMTargetMachineEmitToFile
-
-pub const AttributeIndex = c_uint;
-pub const Bool = c_int;
-
-pub const Builder = c.LLVMBuilderRef.Child.Child;
-pub const Context = c.LLVMContextRef.Child.Child;
-pub const Module = c.LLVMModuleRef.Child.Child;
-pub const Value = c.LLVMValueRef.Child.Child;
-pub const Type = c.LLVMTypeRef.Child.Child;
-pub const BasicBlock = c.LLVMBasicBlockRef.Child.Child;
-pub const Attribute = c.LLVMAttributeRef.Child.Child;
-pub const Target = c.LLVMTargetRef.Child.Child;
-pub const TargetMachine = c.LLVMTargetMachineRef.Child.Child;
-pub const TargetData = c.LLVMTargetDataRef.Child.Child;
-pub const DIBuilder = c.ZigLLVMDIBuilder;
-pub const DIFile = c.ZigLLVMDIFile;
-pub const DICompileUnit = c.ZigLLVMDICompileUnit;
-
-pub const ABIAlignmentOfType = c.LLVMABIAlignmentOfType;
-pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex;
-pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag;
-pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag;
-pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation;
-pub const ConstAllOnes = c.LLVMConstAllOnes;
-pub const ConstArray = c.LLVMConstArray;
-pub const ConstBitCast = c.LLVMConstBitCast;
-pub const ConstIntOfArbitraryPrecision = c.LLVMConstIntOfArbitraryPrecision;
-pub const ConstNeg = c.LLVMConstNeg;
-pub const ConstStructInContext = c.LLVMConstStructInContext;
-pub const DIBuilderFinalize = c.ZigLLVMDIBuilderFinalize;
-pub const DisposeBuilder = c.LLVMDisposeBuilder;
-pub const DisposeDIBuilder = c.ZigLLVMDisposeDIBuilder;
-pub const DisposeMessage = c.LLVMDisposeMessage;
-pub const DisposeModule = c.LLVMDisposeModule;
-pub const DisposeTargetData = c.LLVMDisposeTargetData;
-pub const DisposeTargetMachine = c.LLVMDisposeTargetMachine;
-pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext;
-pub const DumpModule = c.LLVMDumpModule;
-pub const FP128TypeInContext = c.LLVMFP128TypeInContext;
-pub const FloatTypeInContext = c.LLVMFloatTypeInContext;
-pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName;
-pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext;
-pub const GetUndef = c.LLVMGetUndef;
-pub const HalfTypeInContext = c.LLVMHalfTypeInContext;
-pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers;
-pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters;
-pub const InitializeAllTargetInfos = c.LLVMInitializeAllTargetInfos;
-pub const InitializeAllTargetMCs = c.LLVMInitializeAllTargetMCs;
-pub const InitializeAllTargets = c.LLVMInitializeAllTargets;
-pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext;
-pub const Int128TypeInContext = c.LLVMInt128TypeInContext;
-pub const Int16TypeInContext = c.LLVMInt16TypeInContext;
-pub const Int1TypeInContext = c.LLVMInt1TypeInContext;
-pub const Int32TypeInContext = c.LLVMInt32TypeInContext;
-pub const Int64TypeInContext = c.LLVMInt64TypeInContext;
-pub const Int8TypeInContext = c.LLVMInt8TypeInContext;
-pub const IntPtrTypeForASInContext = c.LLVMIntPtrTypeForASInContext;
-pub const IntPtrTypeInContext = c.LLVMIntPtrTypeInContext;
-pub const LabelTypeInContext = c.LLVMLabelTypeInContext;
-pub const MDNodeInContext = c.LLVMMDNodeInContext;
-pub const MDStringInContext = c.LLVMMDStringInContext;
-pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext;
-pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext;
-pub const SetAlignment = c.LLVMSetAlignment;
-pub const SetDataLayout = c.LLVMSetDataLayout;
-pub const SetGlobalConstant = c.LLVMSetGlobalConstant;
-pub const SetInitializer = c.LLVMSetInitializer;
-pub const SetLinkage = c.LLVMSetLinkage;
-pub const SetTarget = c.LLVMSetTarget;
-pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr;
-pub const SetVolatile = c.LLVMSetVolatile;
-pub const StructTypeInContext = c.LLVMStructTypeInContext;
-pub const TokenTypeInContext = c.LLVMTokenTypeInContext;
-pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext;
-pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext;
-
-pub const AddGlobal = LLVMAddGlobal;
-extern fn LLVMAddGlobal(M: *Module, Ty: *Type, Name: [*:0]const u8) ?*Value;
-
-pub const ConstStringInContext = LLVMConstStringInContext;
-extern fn LLVMConstStringInContext(C: *Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) ?*Value;
-
-pub const ConstInt = LLVMConstInt;
-extern fn LLVMConstInt(IntTy: *Type, N: c_ulonglong, SignExtend: Bool) ?*Value;
-
-pub const BuildLoad = LLVMBuildLoad;
-extern fn LLVMBuildLoad(arg0: *Builder, PointerVal: *Value, Name: [*:0]const u8) ?*Value;
-
-pub const ConstNull = LLVMConstNull;
-extern fn LLVMConstNull(Ty: *Type) ?*Value;
-
-pub const CreateStringAttribute = LLVMCreateStringAttribute;
-extern fn LLVMCreateStringAttribute(
-    C: *Context,
-    K: [*]const u8,
-    KLength: c_uint,
-    V: [*]const u8,
-    VLength: c_uint,
-) ?*Attribute;
-
-pub const CreateEnumAttribute = LLVMCreateEnumAttribute;
-extern fn LLVMCreateEnumAttribute(C: *Context, KindID: c_uint, Val: u64) ?*Attribute;
-
-pub const AddFunction = LLVMAddFunction;
-extern fn LLVMAddFunction(M: *Module, Name: [*:0]const u8, FunctionTy: *Type) ?*Value;
-
-pub const CreateCompileUnit = ZigLLVMCreateCompileUnit;
-extern fn ZigLLVMCreateCompileUnit(
-    dibuilder: *DIBuilder,
-    lang: c_uint,
-    difile: *DIFile,
-    producer: [*:0]const u8,
-    is_optimized: bool,
-    flags: [*:0]const u8,
-    runtime_version: c_uint,
-    split_name: [*:0]const u8,
-    dwo_id: u64,
-    emit_debug_info: bool,
-) ?*DICompileUnit;
-
-pub const CreateFile = ZigLLVMCreateFile;
-extern fn ZigLLVMCreateFile(dibuilder: *DIBuilder, filename: [*:0]const u8, directory: [*:0]const u8) ?*DIFile;
-
-pub const ArrayType = LLVMArrayType;
-extern fn LLVMArrayType(ElementType: *Type, ElementCount: c_uint) ?*Type;
-
-pub const CreateDIBuilder = ZigLLVMCreateDIBuilder;
-extern fn ZigLLVMCreateDIBuilder(module: *Module, allow_unresolved: bool) ?*DIBuilder;
-
-pub const PointerType = LLVMPointerType;
-extern fn LLVMPointerType(ElementType: *Type, AddressSpace: c_uint) ?*Type;
-
-pub const CreateBuilderInContext = LLVMCreateBuilderInContext;
-extern fn LLVMCreateBuilderInContext(C: *Context) ?*Builder;
-
-pub const IntTypeInContext = LLVMIntTypeInContext;
-extern fn LLVMIntTypeInContext(C: *Context, NumBits: c_uint) ?*Type;
-
-pub const ModuleCreateWithNameInContext = LLVMModuleCreateWithNameInContext;
-extern fn LLVMModuleCreateWithNameInContext(ModuleID: [*:0]const u8, C: *Context) ?*Module;
-
-pub const VoidTypeInContext = LLVMVoidTypeInContext;
-extern fn LLVMVoidTypeInContext(C: *Context) ?*Type;
-
-pub const ContextCreate = LLVMContextCreate;
-extern fn LLVMContextCreate() ?*Context;
-
-pub const ContextDispose = LLVMContextDispose;
-extern fn LLVMContextDispose(C: *Context) void;
-
-pub const CopyStringRepOfTargetData = LLVMCopyStringRepOfTargetData;
-extern fn LLVMCopyStringRepOfTargetData(TD: *TargetData) ?[*:0]u8;
-
-pub const CreateTargetDataLayout = LLVMCreateTargetDataLayout;
-extern fn LLVMCreateTargetDataLayout(T: *TargetMachine) ?*TargetData;
-
-pub const CreateTargetMachine = ZigLLVMCreateTargetMachine;
-extern fn ZigLLVMCreateTargetMachine(
-    T: *Target,
-    Triple: [*:0]const u8,
-    CPU: [*:0]const u8,
-    Features: [*:0]const u8,
-    Level: CodeGenOptLevel,
-    Reloc: RelocMode,
-    CodeModel: CodeModel,
-    function_sections: bool,
-) ?*TargetMachine;
-
-pub const GetHostCPUName = LLVMGetHostCPUName;
-extern fn LLVMGetHostCPUName() ?[*:0]u8;
-
-pub const GetNativeFeatures = ZigLLVMGetNativeFeatures;
-extern fn ZigLLVMGetNativeFeatures() ?[*:0]u8;
-
-pub const GetElementType = LLVMGetElementType;
-extern fn LLVMGetElementType(Ty: *Type) *Type;
-
-pub const TypeOf = LLVMTypeOf;
-extern fn LLVMTypeOf(Val: *Value) *Type;
-
-pub const BuildStore = LLVMBuildStore;
-extern fn LLVMBuildStore(arg0: *Builder, Val: *Value, Ptr: *Value) ?*Value;
-
-pub const BuildAlloca = LLVMBuildAlloca;
-extern fn LLVMBuildAlloca(arg0: *Builder, Ty: *Type, Name: ?[*:0]const u8) ?*Value;
-
-pub const ConstInBoundsGEP = LLVMConstInBoundsGEP;
-pub extern fn LLVMConstInBoundsGEP(ConstantVal: *Value, ConstantIndices: [*]*Value, NumIndices: c_uint) ?*Value;
-
-pub const GetTargetFromTriple = LLVMGetTargetFromTriple;
-extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **Target, ErrorMessage: ?*[*:0]u8) Bool;
-
-pub const VerifyModule = LLVMVerifyModule;
-extern fn LLVMVerifyModule(M: *Module, Action: VerifierFailureAction, OutMessage: *?[*:0]u8) Bool;
-
-pub const GetInsertBlock = LLVMGetInsertBlock;
-extern fn LLVMGetInsertBlock(Builder: *Builder) *BasicBlock;
-
-pub const FunctionType = LLVMFunctionType;
-extern fn LLVMFunctionType(
-    ReturnType: *Type,
-    ParamTypes: [*]*Type,
-    ParamCount: c_uint,
-    IsVarArg: Bool,
-) ?*Type;
-
-pub const GetParam = LLVMGetParam;
-extern fn LLVMGetParam(Fn: *Value, Index: c_uint) *Value;
-
-pub const AppendBasicBlockInContext = LLVMAppendBasicBlockInContext;
-extern fn LLVMAppendBasicBlockInContext(C: *Context, Fn: *Value, Name: [*:0]const u8) ?*BasicBlock;
-
-pub const PositionBuilderAtEnd = LLVMPositionBuilderAtEnd;
-extern fn LLVMPositionBuilderAtEnd(Builder: *Builder, Block: *BasicBlock) void;
-
-pub const AbortProcessAction = VerifierFailureAction.LLVMAbortProcessAction;
-pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction;
-pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction;
-pub const VerifierFailureAction = c.LLVMVerifierFailureAction;
-
-pub const CodeGenLevelNone = CodeGenOptLevel.LLVMCodeGenLevelNone;
-pub const CodeGenLevelLess = CodeGenOptLevel.LLVMCodeGenLevelLess;
-pub const CodeGenLevelDefault = CodeGenOptLevel.LLVMCodeGenLevelDefault;
-pub const CodeGenLevelAggressive = CodeGenOptLevel.LLVMCodeGenLevelAggressive;
-pub const CodeGenOptLevel = c.LLVMCodeGenOptLevel;
-
-pub const RelocDefault = RelocMode.LLVMRelocDefault;
-pub const RelocStatic = RelocMode.LLVMRelocStatic;
-pub const RelocPIC = RelocMode.LLVMRelocPIC;
-pub const RelocDynamicNoPic = RelocMode.LLVMRelocDynamicNoPic;
-pub const RelocMode = c.LLVMRelocMode;
-
-pub const CodeModelDefault = CodeModel.LLVMCodeModelDefault;
-pub const CodeModelJITDefault = CodeModel.LLVMCodeModelJITDefault;
-pub const CodeModelSmall = CodeModel.LLVMCodeModelSmall;
-pub const CodeModelKernel = CodeModel.LLVMCodeModelKernel;
-pub const CodeModelMedium = CodeModel.LLVMCodeModelMedium;
-pub const CodeModelLarge = CodeModel.LLVMCodeModelLarge;
-pub const CodeModel = c.LLVMCodeModel;
-
-pub const EmitAssembly = EmitOutputType.ZigLLVM_EmitAssembly;
-pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary;
-pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr;
-pub const EmitOutputType = c.ZigLLVM_EmitOutputType;
-
-pub const CCallConv = CallConv.LLVMCCallConv;
-pub const FastCallConv = CallConv.LLVMFastCallConv;
-pub const ColdCallConv = CallConv.LLVMColdCallConv;
-pub const WebKitJSCallConv = CallConv.LLVMWebKitJSCallConv;
-pub const AnyRegCallConv = CallConv.LLVMAnyRegCallConv;
-pub const X86StdcallCallConv = CallConv.LLVMX86StdcallCallConv;
-pub const X86FastcallCallConv = CallConv.LLVMX86FastcallCallConv;
-pub const CallConv = c.LLVMCallConv;
-
-pub const CallAttr = extern enum {
-    Auto,
-    NeverTail,
-    NeverInline,
-    AlwaysTail,
-    AlwaysInline,
-};
-
-fn removeNullability(comptime T: type) type {
-    comptime assert(@typeInfo(T).Pointer.size == .C);
-    return *T.Child;
-}
-
-pub const BuildRet = LLVMBuildRet;
-extern fn LLVMBuildRet(arg0: *Builder, V: ?*Value) ?*Value;
-
-pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile;
-extern fn ZigLLVMTargetMachineEmitToFile(
-    targ_machine_ref: *TargetMachine,
-    module_ref: *Module,
-    filename: [*:0]const u8,
-    output_type: EmitOutputType,
-    error_message: *[*:0]u8,
-    is_debug: bool,
-    is_small: bool,
+//! We do this instead of @cImport because the self-hosted compiler is easier
+//! to bootstrap if it does not depend on translate-c.
+
+pub extern fn ZigLLDLink(
+    oformat: ZigLLVM_ObjectFormatType,
+    args: [*:null]const ?[*:0]const u8,
+    arg_count: usize,
+    append_diagnostic: fn (context: usize, ptr: [*]const u8, len: usize) callconv(.C) void,
+    context_stdout: usize,
+    context_stderr: usize,
 ) bool;
 
-pub const BuildCall = ZigLLVMBuildCall;
-extern fn ZigLLVMBuildCall(B: *Builder, Fn: *Value, Args: [*]*Value, NumArgs: c_uint, CC: CallConv, fn_inline: CallAttr, Name: [*:0]const u8) ?*Value;
-
-pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage;
+pub const ZigLLVM_ObjectFormatType = extern enum(c_int) {
+    Unknown,
+    COFF,
+    ELF,
+    MachO,
+    Wasm,
+    XCOFF,
+};
src-self-hosted/main.zig
@@ -191,7 +191,14 @@ const usage_build_generic =
     \\    ReleaseSmall            Optimize for small binary, safety off
     \\  -fPIC                     Force-enable Position Independent Code
     \\  -fno-PIC                  Force-disable Position Independent Code
+    \\  -fstack-check             Enable stack probing in unsafe builds
+    \\  -fno-stack-check          Disable stack probing in safe builds
+    \\  -fsanitize-c              Enable C undefined behavior detection in unsafe builds
+    \\  -fno-sanitize-c           Disable C undefined behavior detection in safe builds
+    \\  -fvalgrind                Include valgrind client requests in release builds
+    \\  -fno-valgrind             Omit valgrind client requests in debug builds
     \\  --strip                   Exclude debug symbols
+    \\  --single-threaded         Code assumes it is only used single-threaded
     \\  -ofmt=[mode]              Override target object format
     \\    elf                     Executable and Linking Format
     \\    c                       Compile to C source code
@@ -262,6 +269,7 @@ pub fn buildOutputType(
     var root_src_file: ?[]const u8 = null;
     var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 };
     var strip = false;
+    var single_threaded = false;
     var watch = false;
     var debug_tokenize = false;
     var debug_ast_tree = false;
@@ -287,6 +295,8 @@ pub fn buildOutputType(
     var enable_cache: ?bool = null;
     var want_pic: ?bool = null;
     var want_sanitize_c: ?bool = null;
+    var want_stack_check: ?bool = null;
+    var want_valgrind: ?bool = null;
     var rdynamic: bool = false;
     var only_pp_or_asm = false;
     var linker_script: ?[]const u8 = null;
@@ -320,7 +330,7 @@ pub fn buildOutputType(
     var rpath_list = std.ArrayList([]const u8).init(gpa);
     defer rpath_list.deinit();
 
-    var c_source_files = std.ArrayList([]const u8).init(gpa);
+    var c_source_files = std.ArrayList(Module.CSourceFile).init(gpa);
     defer c_source_files.deinit();
 
     var link_objects = std.ArrayList([]const u8).init(gpa);
@@ -463,6 +473,18 @@ pub fn buildOutputType(
                     want_pic = true;
                 } else if (mem.eql(u8, arg, "-fno-PIC")) {
                     want_pic = false;
+                } else if (mem.eql(u8, arg, "-fstack-check")) {
+                    want_stack_check = true;
+                } else if (mem.eql(u8, arg, "-fno-stack-check")) {
+                    want_stack_check = false;
+                } else if (mem.eql(u8, arg, "-fsanitize-c")) {
+                    want_sanitize_c = true;
+                } else if (mem.eql(u8, arg, "-fno-sanitize-c")) {
+                    want_sanitize_c = false;
+                } else if (mem.eql(u8, arg, "-fvalgrind")) {
+                    want_valgrind = true;
+                } else if (mem.eql(u8, arg, "-fno-valgrind")) {
+                    want_valgrind = false;
                 } else if (mem.eql(u8, arg, "-fLLVM")) {
                     use_llvm = true;
                 } else if (mem.eql(u8, arg, "-fno-LLVM")) {
@@ -501,6 +523,8 @@ pub fn buildOutputType(
                     link_mode = .Static;
                 } else if (mem.eql(u8, arg, "--strip")) {
                     strip = true;
+                } else if (mem.eql(u8, arg, "--single-threaded")) {
+                    single_threaded = true;
                 } else if (mem.eql(u8, arg, "--eh-frame-hdr")) {
                     link_eh_frame_hdr = true;
                 } else if (mem.eql(u8, arg, "-Bsymbolic")) {
@@ -541,7 +565,8 @@ pub fn buildOutputType(
             {
                 try link_objects.append(arg);
             } else if (Module.hasAsmExt(arg) or Module.hasCExt(arg) or Module.hasCppExt(arg)) {
-                try c_source_files.append(arg);
+                // TODO a way to pass extra flags on the CLI
+                try c_source_files.append(.{ .src_path = arg });
             } else if (mem.endsWith(u8, arg, ".so") or
                 mem.endsWith(u8, arg, ".dylib") or
                 mem.endsWith(u8, arg, ".dll"))
@@ -586,7 +611,7 @@ pub fn buildOutputType(
                 .positional => {
                     const file_ext = Module.classifyFileExt(mem.spanZ(it.only_arg));
                     switch (file_ext) {
-                        .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(it.only_arg),
+                        .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(.{ .src_path = it.only_arg }),
                         .unknown, .so => try link_objects.append(it.only_arg),
                     }
                 },
@@ -812,7 +837,7 @@ pub fn buildOutputType(
             //        .yes => |p| p,
             //        else => c_source_file.source_path,
             //    };
-            //    const basename = std.fs.path.basename(src_path);
+            //    const basename = fs.path.basename(src_path);
             //    c_source_file.preprocessor_only_basename = basename;
             //}
             //emit_bin = .no;
@@ -839,7 +864,7 @@ pub fn buildOutputType(
             const basename = fs.path.basename(file);
             break :blk mem.split(basename, ".").next().?;
         } else if (c_source_files.items.len == 1) {
-            const basename = fs.path.basename(c_source_files.items[0]);
+            const basename = fs.path.basename(c_source_files.items[0].src_path);
             break :blk mem.split(basename, ".").next().?;
         } else if (link_objects.items.len == 1) {
             const basename = fs.path.basename(link_objects.items[0]);
@@ -966,19 +991,71 @@ pub fn buildOutputType(
         }
     };
 
-    const bin_path = switch (emit_bin) {
-        .no => {
-            fatal("-fno-emit-bin not supported yet", .{});
+    var cleanup_emit_bin_dir: ?fs.Dir = null;
+    defer if (cleanup_emit_bin_dir) |*dir| dir.close();
+
+    const emit_bin_loc: ?Module.EmitLoc = switch (emit_bin) {
+        .no => null,
+        .yes_default_path => Module.EmitLoc{
+            .directory = .{ .path = null, .handle = fs.cwd() },
+            .basename = try std.zig.binNameAlloc(
+                arena,
+                root_name,
+                target_info.target,
+                output_mode,
+                link_mode,
+                object_format,
+            ),
+        },
+        .yes => |full_path| b: {
+            const basename = fs.path.basename(full_path);
+            if (fs.path.dirname(full_path)) |dirname| {
+                const handle = try fs.cwd().openDir(dirname, .{});
+                cleanup_emit_bin_dir = handle;
+                break :b Module.EmitLoc{
+                    .basename = basename,
+                    .directory = .{
+                        .path = dirname,
+                        .handle = handle,
+                    },
+                };
+            } else {
+                break :b Module.EmitLoc{
+                    .basename = basename,
+                    .directory = .{ .path = null, .handle = fs.cwd() },
+                };
+            }
+        },
+    };
+
+    var cleanup_emit_h_dir: ?fs.Dir = null;
+    defer if (cleanup_emit_h_dir) |*dir| dir.close();
+
+    const emit_h_loc: ?Module.EmitLoc = switch (emit_h) {
+        .no => null,
+        .yes_default_path => Module.EmitLoc{
+            .directory = .{ .path = null, .handle = fs.cwd() },
+            .basename = try std.fmt.allocPrint(arena, "{}.h", .{root_name}),
+        },
+        .yes => |full_path| b: {
+            const basename = fs.path.basename(full_path);
+            if (fs.path.dirname(full_path)) |dirname| {
+                const handle = try fs.cwd().openDir(dirname, .{});
+                cleanup_emit_h_dir = handle;
+                break :b Module.EmitLoc{
+                    .basename = basename,
+                    .directory = .{
+                        .path = dirname,
+                        .handle = handle,
+                    },
+                };
+            } else {
+                break :b Module.EmitLoc{
+                    .basename = basename,
+                    .directory = .{ .path = null, .handle = fs.cwd() },
+                };
+            }
         },
-        .yes_default_path => try std.zig.binNameAlloc(
-            arena,
-            root_name,
-            target_info.target,
-            output_mode,
-            link_mode,
-            object_format,
-        ),
-        .yes => |p| p,
     };
 
     const zir_out_path: ?[]const u8 = switch (emit_zir) {
@@ -995,19 +1072,13 @@ pub fn buildOutputType(
     };
 
     const root_pkg = if (root_src_file) |src_path| try Package.create(gpa, fs.cwd(), ".", src_path) else null;
-    defer if (root_pkg) |pkg| pkg.destroy();
-
-    const emit_h_path: ?[]const u8 = switch (emit_h) {
-        .yes => |p| p,
-        .no => null,
-        .yes_default_path => try std.fmt.allocPrint(arena, "{}.h", .{root_name}),
-    };
+    defer if (root_pkg) |pkg| pkg.destroy(gpa);
 
     const self_exe_path = try fs.selfExePathAlloc(arena);
-    const zig_lib_dir = introspect.resolveZigLibDir(gpa) catch |err| {
+    var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
         fatal("unable to find zig installation directory: {}\n", .{@errorName(err)});
     };
-    defer gpa.free(zig_lib_dir);
+    defer zig_lib_directory.handle.close();
 
     const random_seed = blk: {
         var random_seed: u64 = undefined;
@@ -1025,17 +1096,32 @@ pub fn buildOutputType(
         };
     }
 
+    const cache_parent_dir = if (root_pkg) |pkg| pkg.root_src_directory.handle else fs.cwd();
+    var cache_dir = try cache_parent_dir.makeOpenPath("zig-cache", .{});
+    defer cache_dir.close();
+    const zig_cache_directory: Module.Directory = .{
+        .handle = cache_dir,
+        .path = blk: {
+            if (root_pkg) |pkg| {
+                if (pkg.root_src_directory.path) |p| {
+                    break :blk try fs.path.join(arena, &[_][]const u8{ p, "zig-cache" });
+                }
+            }
+            break :blk "zig-cache";
+        },
+    };
+
     const module = Module.create(gpa, .{
-        .zig_lib_dir = zig_lib_dir,
+        .zig_lib_directory = zig_lib_directory,
+        .zig_cache_directory = zig_cache_directory,
         .root_name = root_name,
         .target = target_info.target,
         .is_native_os = cross_target.isNativeOs(),
         .dynamic_linker = target_info.dynamic_linker.get(),
         .output_mode = output_mode,
         .root_pkg = root_pkg,
-        .bin_file_dir_path = null,
-        .bin_file_dir = fs.cwd(),
-        .bin_file_path = bin_path,
+        .emit_bin = emit_bin_loc,
+        .emit_h = emit_h_loc,
         .link_mode = link_mode,
         .object_format = object_format,
         .optimize_mode = build_mode,
@@ -1049,11 +1135,12 @@ pub fn buildOutputType(
         .framework_dirs = framework_dirs.items,
         .frameworks = frameworks.items,
         .system_libs = system_libs.items,
-        .emit_h = emit_h_path,
         .link_libc = link_libc,
         .link_libcpp = link_libcpp,
         .want_pic = want_pic,
         .want_sanitize_c = want_sanitize_c,
+        .want_stack_check = want_stack_check,
+        .want_valgrind = want_valgrind,
         .use_llvm = use_llvm,
         .use_lld = use_lld,
         .use_clang = use_clang,
@@ -1070,11 +1157,14 @@ pub fn buildOutputType(
         .link_eh_frame_hdr = link_eh_frame_hdr,
         .stack_size_override = stack_size_override,
         .strip = strip,
+        .single_threaded = single_threaded,
         .self_exe_path = self_exe_path,
         .rand = &default_prng.random,
         .clang_passthrough_mode = arg_mode != .build,
         .version = version,
         .libc_installation = if (libc_installation) |*lci| lci else null,
+        .debug_cc = debug_cc,
+        .debug_link = debug_link,
     }) catch |err| {
         fatal("unable to create module: {}", .{@errorName(err)});
     };
@@ -1121,9 +1211,7 @@ pub fn buildOutputType(
 }
 
 fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void {
-    var timer = try std.time.Timer.start();
     try module.update();
-    const update_nanos = timer.read();
 
     var errors = try module.getAllErrorsAlloc();
     defer errors.deinit(module.gpa);
src-self-hosted/Module.zig
@@ -25,6 +25,8 @@ const astgen = @import("astgen.zig");
 const zir_sema = @import("zir_sema.zig");
 const build_options = @import("build_options");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
+const glibc = @import("glibc.zig");
+const fatal = @import("main.zig").fatal;
 
 /// General-purpose allocator. Used for both temporary and long-term storage.
 gpa: *Allocator,
@@ -91,17 +93,20 @@ sanitize_c: bool,
 /// Otherwise we attempt to parse the error messages and expose them via the Module API.
 /// This is `true` for `zig cc`, `zig c++`, and `zig translate-c`.
 clang_passthrough_mode: bool,
+/// Whether to print clang argvs to stdout.
+debug_cc: bool,
 
 /// Error tags and their values, tag names are duped with mod.gpa.
 global_error_set: std.StringHashMapUnmanaged(u16) = .{},
 
-c_source_files: []const []const u8,
+c_source_files: []const CSourceFile,
 clang_argv: []const []const u8,
 cache: std.cache_hash.CacheHash,
 /// Path to own executable for invoking `zig clang`.
 self_exe_path: ?[]const u8,
-zig_lib_dir: []const u8,
-zig_cache_dir_path: []const u8,
+zig_lib_directory: Directory,
+zig_cache_directory: Directory,
+zig_cache_artifact_directory: Directory,
 libc_include_dir_list: []const []const u8,
 rand: *std.rand.Random,
 
@@ -118,8 +123,19 @@ libunwind_static_lib: ?[]const u8 = null,
 /// and resolved before calling linker.flush().
 libc_static_lib: ?[]const u8 = null,
 
+/// For example `Scrt1.o` and `libc.so.6`. These are populated after building libc from source,
+/// The set of needed CRT (C runtime) files differs depending on the target and compilation settings.
+/// The key is the basename, and the value is the absolute path to the completed build artifact.
+crt_files: std.StringHashMapUnmanaged([]const u8) = .{},
+
 pub const InnerError = error{ OutOfMemory, AnalysisFail };
 
+/// For passing to a C compiler.
+pub const CSourceFile = struct {
+    src_path: []const u8,
+    extra_flags: []const []const u8 = &[0][]const u8{},
+};
+
 const WorkItem = union(enum) {
     /// Write the machine code for a Decl to the output file.
     codegen_decl: *Decl,
@@ -133,6 +149,11 @@ const WorkItem = union(enum) {
     /// Invoke the Clang compiler to create an object file, which gets linked
     /// with the Module.
     c_object: *CObject,
+
+    /// one of the glibc static objects
+    glibc_crt_file: glibc.CRTFile,
+    /// one of the glibc shared objects
+    glibc_so: *const glibc.Lib,
 };
 
 pub const Export = struct {
@@ -701,7 +722,7 @@ pub const Scope = struct {
         pub fn getSource(self: *File, module: *Module) ![:0]const u8 {
             switch (self.source) {
                 .unloaded => {
-                    const source = try module.root_pkg.?.root_src_dir.readFileAllocOptions(
+                    const source = try module.root_pkg.?.root_src_directory.handle.readFileAllocOptions(
                         module.gpa,
                         self.sub_file_path,
                         std.math.maxInt(u32),
@@ -805,7 +826,7 @@ pub const Scope = struct {
         pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 {
             switch (self.source) {
                 .unloaded => {
-                    const source = try module.root_pkg.?.root_src_dir.readFileAllocOptions(
+                    const source = try module.root_pkg.?.root_src_directory.handle.readFileAllocOptions(
                         module.gpa,
                         self.sub_file_path,
                         std.math.maxInt(u32),
@@ -937,18 +958,35 @@ pub const AllErrors = struct {
     }
 };
 
+pub const Directory = struct {
+    /// This field is redundant for operations that can act on the open directory handle
+    /// directly, but it is needed when passing the directory to a child process.
+    /// `null` means cwd.
+    path: ?[]const u8,
+    handle: std.fs.Dir,
+};
+
+pub const EmitLoc = struct {
+    /// If this is `null` it means the file will be output to the cache directory.
+    /// When provided, both the open file handle and the path name must outlive the `Module`.
+    directory: ?Module.Directory,
+    /// This may not have sub-directories in it.
+    basename: []const u8,
+};
+
 pub const InitOptions = struct {
-    zig_lib_dir: []const u8,
+    zig_lib_directory: Directory,
+    zig_cache_directory: Directory,
     target: Target,
     root_name: []const u8,
     root_pkg: ?*Package,
     output_mode: std.builtin.OutputMode,
     rand: *std.rand.Random,
     dynamic_linker: ?[]const u8 = null,
-    bin_file_dir_path: ?[]const u8 = null,
-    bin_file_dir: ?std.fs.Dir = null,
-    bin_file_path: []const u8,
-    emit_h: ?[]const u8 = null,
+    /// `null` means to not emit a binary file.
+    emit_bin: ?EmitLoc,
+    /// `null` means to not emit a C header file.
+    emit_h: ?EmitLoc = null,
     link_mode: ?std.builtin.LinkMode = null,
     object_format: ?std.builtin.ObjectFormat = null,
     optimize_mode: std.builtin.Mode = .Debug,
@@ -957,7 +995,7 @@ pub const InitOptions = struct {
     lld_argv: []const []const u8 = &[0][]const u8{},
     lib_dirs: []const []const u8 = &[0][]const u8{},
     rpath_list: []const []const u8 = &[0][]const u8{},
-    c_source_files: []const []const u8 = &[0][]const u8{},
+    c_source_files: []const CSourceFile = &[0]CSourceFile{},
     link_objects: []const []const u8 = &[0][]const u8{},
     framework_dirs: []const []const u8 = &[0][]const u8{},
     frameworks: []const []const u8 = &[0][]const u8{},
@@ -966,11 +1004,14 @@ pub const InitOptions = struct {
     link_libcpp: bool = false,
     want_pic: ?bool = null,
     want_sanitize_c: ?bool = null,
+    want_stack_check: ?bool = null,
+    want_valgrind: ?bool = null,
     use_llvm: ?bool = null,
     use_lld: ?bool = null,
     use_clang: ?bool = null,
     rdynamic: bool = false,
     strip: bool = false,
+    single_threaded: bool = false,
     is_native_os: bool,
     link_eh_frame_hdr: bool = false,
     linker_script: ?[]const u8 = null,
@@ -984,6 +1025,8 @@ pub const InitOptions = struct {
     linker_z_nodelete: bool = false,
     linker_z_defs: bool = false,
     clang_passthrough_mode: bool = false,
+    debug_cc: bool = false,
+    debug_link: bool = false,
     stack_size_override: ?u64 = null,
     self_exe_path: ?[]const u8 = null,
     version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 },
@@ -1057,17 +1100,126 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
 
         const libc_dirs = try detectLibCIncludeDirs(
             arena,
-            options.zig_lib_dir,
+            options.zig_lib_directory.path.?,
             options.target,
             options.is_native_os,
             options.link_libc,
             options.libc_installation,
         );
 
+        const must_pic: bool = b: {
+            if (target_util.requiresPIC(options.target, options.link_libc))
+                break :b true;
+            break :b link_mode == .Dynamic;
+        };
+        const pic = options.want_pic orelse must_pic;
+
+        if (options.emit_h != null) fatal("-femit-h not supported yet", .{}); // TODO
+
+        const emit_bin = options.emit_bin orelse fatal("-fno-emit-bin not supported yet", .{}); // TODO
+
+        // Make a decision on whether to use Clang for translate-c and compiling C files.
+        const use_clang = if (options.use_clang) |explicit| explicit else blk: {
+            if (build_options.have_llvm) {
+                // Can't use it if we don't have it!
+                break :blk false;
+            }
+            // It's not planned to do our own translate-c or C compilation.
+            break :blk true;
+        };
+
+        const is_safe_mode = switch (options.optimize_mode) {
+            .Debug, .ReleaseSafe => true,
+            .ReleaseFast, .ReleaseSmall => false,
+        };
+
+        const sanitize_c = options.want_sanitize_c orelse is_safe_mode;
+
+        const stack_check: bool = b: {
+            if (!target_util.supportsStackProbing(options.target))
+                break :b false;
+            break :b options.want_stack_check orelse is_safe_mode;
+        };
+
+        const valgrind: bool = b: {
+            if (!target_util.hasValgrindSupport(options.target))
+                break :b false;
+            break :b options.want_valgrind orelse (options.optimize_mode == .Debug);
+        };
+
+        const single_threaded = options.single_threaded or target_util.isSingleThreaded(options.target);
+
+        // We put everything into the cache hash that *cannot be modified during an incremental update*.
+        // For example, one cannot change the target between updates, but one can change source files,
+        // so the target goes into the cache hash, but source files do not. This is so that we can
+        // find the same binary and incrementally update it even if there are modified source files.
+        // We do this even if outputting to the current directory because (1) this cache_hash instance
+        // will be the "parent" of other cache_hash instances such as for C objects, (2) we need
+        // a place for intermediate build artifacts, such as a .o file to be linked with LLD, and (3)
+        // we need somewhere to store serialization of incremental compilation metadata.
+        var cache = try std.cache_hash.CacheHash.init(gpa, options.zig_cache_directory.handle, "h");
+        errdefer cache.release();
+
+        // Now we will prepare hash state initializations to avoid redundantly computing hashes.
+        // First we add common things between things that apply to zig source and all c source files.
+        cache.addBytes(build_options.version);
+        cache.add(options.optimize_mode);
+        cache.add(options.target.cpu.arch);
+        cache.addBytes(options.target.cpu.model.name);
+        cache.add(options.target.cpu.features.ints);
+        cache.add(options.target.os.tag);
+        switch (options.target.os.tag) {
+            .linux => {
+                cache.add(options.target.os.version_range.linux.range.min);
+                cache.add(options.target.os.version_range.linux.range.max);
+                cache.add(options.target.os.version_range.linux.glibc);
+            },
+            .windows => {
+                cache.add(options.target.os.version_range.windows.min);
+                cache.add(options.target.os.version_range.windows.max);
+            },
+            .freebsd,
+            .macosx,
+            .ios,
+            .tvos,
+            .watchos,
+            .netbsd,
+            .openbsd,
+            .dragonfly,
+            => {
+                cache.add(options.target.os.version_range.semver.min);
+                cache.add(options.target.os.version_range.semver.max);
+            },
+            else => {},
+        }
+        cache.add(options.target.abi);
+        cache.add(ofmt);
+        cache.add(pic);
+        cache.add(stack_check);
+        cache.add(sanitize_c);
+        cache.add(valgrind);
+        cache.add(link_mode);
+        cache.add(options.strip);
+        cache.add(single_threaded);
+        // TODO audit this and make sure everything is in it
+
+        // We don't care whether we find something there, just show us the digest.
+        const digest = (try cache.hit()) orelse cache.final();
+
+        const artifact_sub_dir = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
+        var artifact_dir = try options.zig_cache_directory.handle.makeOpenPath(artifact_sub_dir, .{});
+        errdefer artifact_dir.close();
+        const zig_cache_artifact_directory: Directory = .{
+            .handle = artifact_dir,
+            .path = if (options.zig_cache_directory.path) |p|
+                try std.fs.path.join(arena, &[_][]const u8{ p, artifact_sub_dir })
+            else
+                artifact_sub_dir,
+        };
+
         const bin_file = try link.File.openPath(gpa, .{
-            .dir = options.bin_file_dir orelse std.fs.cwd(),
-            .dir_path = options.bin_file_dir_path,
-            .sub_path = options.bin_file_path,
+            .directory = emit_bin.directory orelse zig_cache_artifact_directory,
+            .sub_path = emit_bin.basename,
             .root_name = root_name,
             .root_pkg = options.root_pkg,
             .target = options.target,
@@ -1103,6 +1255,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             .override_soname = options.override_soname,
             .version = options.version,
             .libc_installation = libc_dirs.libc_installation,
+            .pic = pic,
+            .valgrind = valgrind,
+            .stack_check = stack_check,
+            .single_threaded = single_threaded,
+            .debug_link = options.debug_link,
         });
         errdefer bin_file.destroy();
 
@@ -1142,83 +1299,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             }
         };
 
-        // We put everything into the cache hash except for the root source file, because we want to
-        // find the same binary and incrementally update it even if the file contents changed.
-        // TODO Look into storing this information in memory rather than on disk and solving
-        // serialization/deserialization of *all* incremental compilation state in a more generic way.
-        const cache_parent_dir = if (options.root_pkg) |root_pkg| root_pkg.root_src_dir else std.fs.cwd();
-        var cache_dir = try cache_parent_dir.makeOpenPath("zig-cache", .{});
-        defer cache_dir.close();
-
-        try cache_dir.makePath("tmp");
-        try cache_dir.makePath("o");
-        // We need this string because of sending paths to clang as a child process.
-        const zig_cache_dir_path = if (options.root_pkg) |root_pkg|
-            try std.fmt.allocPrint(arena, "{}" ++ std.fs.path.sep_str ++ "zig-cache", .{root_pkg.root_src_dir_path})
-        else
-            "zig-cache";
-
-        var cache = try std.cache_hash.CacheHash.init(gpa, cache_dir, "h");
-        errdefer cache.release();
-
-        // Now we will prepare hash state initializations to avoid redundantly computing hashes.
-        // First we add common things between things that apply to zig source and all c source files.
-        cache.addBytes(build_options.version);
-        cache.add(options.optimize_mode);
-        cache.add(options.target.cpu.arch);
-        cache.addBytes(options.target.cpu.model.name);
-        cache.add(options.target.cpu.features.ints);
-        cache.add(options.target.os.tag);
-        switch (options.target.os.tag) {
-            .linux => {
-                cache.add(options.target.os.version_range.linux.range.min);
-                cache.add(options.target.os.version_range.linux.range.max);
-                cache.add(options.target.os.version_range.linux.glibc);
-            },
-            .windows => {
-                cache.add(options.target.os.version_range.windows.min);
-                cache.add(options.target.os.version_range.windows.max);
-            },
-            .freebsd,
-            .macosx,
-            .ios,
-            .tvos,
-            .watchos,
-            .netbsd,
-            .openbsd,
-            .dragonfly,
-            => {
-                cache.add(options.target.os.version_range.semver.min);
-                cache.add(options.target.os.version_range.semver.max);
-            },
-            else => {},
-        }
-        cache.add(options.target.abi);
-        cache.add(ofmt);
-        // TODO PIC (see detect_pic from codegen.cpp)
-        cache.add(bin_file.options.link_mode);
-        cache.add(options.strip);
-
-        // Make a decision on whether to use Clang for translate-c and compiling C files.
-        const use_clang = if (options.use_clang) |explicit| explicit else blk: {
-            if (build_options.have_llvm) {
-                // Can't use it if we don't have it!
-                break :blk false;
-            }
-            // It's not planned to do our own translate-c or C compilation.
-            break :blk true;
-        };
-
-        const sanitize_c: bool = options.want_sanitize_c orelse switch (options.optimize_mode) {
-            .Debug, .ReleaseSafe => true,
-            .ReleaseSmall, .ReleaseFast => false,
-        };
-
         mod.* = .{
             .gpa = gpa,
             .arena_state = arena_allocator.state,
-            .zig_lib_dir = options.zig_lib_dir,
-            .zig_cache_dir_path = zig_cache_dir_path,
+            .zig_lib_directory = options.zig_lib_directory,
+            .zig_cache_directory = options.zig_cache_directory,
+            .zig_cache_artifact_directory = zig_cache_artifact_directory,
             .root_pkg = options.root_pkg,
             .root_scope = root_scope,
             .bin_file = bin_file,
@@ -1233,6 +1319,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             .sanitize_c = sanitize_c,
             .rand = options.rand,
             .clang_passthrough_mode = options.clang_passthrough_mode,
+            .debug_cc = options.debug_cc,
         };
         break :mod mod;
     };
@@ -1245,22 +1332,21 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
         errdefer local_arena.deinit();
 
         const c_object = try local_arena.allocator.create(CObject);
-        const src_path = try local_arena.allocator.dupe(u8, c_source_file);
 
         c_object.* = .{
             .status = .{ .new = {} },
-            .src_path = src_path,
-            .extra_flags = &[0][]const u8{},
+            // TODO why are we duplicating this memory? do we need to?
+            // look into refactoring to turn these 2 fields simply into a CSourceFile
+            .src_path = try local_arena.allocator.dupe(u8, c_source_file.src_path),
+            .extra_flags = try local_arena.allocator.dupe([]const u8, c_source_file.extra_flags),
             .arena = local_arena.state,
         };
         mod.c_object_table.putAssumeCapacityNoClobber(c_object, {});
     }
 
     // If we need to build glibc for the target, add work items for it.
-    if (mod.bin_file.options.link_libc and
-        mod.bin_file.options.libc_installation == null and
-        mod.bin_file.options.target.isGnuLibC())
-    {
+    // We go through the work queue so that building can be done in parallel.
+    if (mod.wantBuildGLibCFromSource()) {
         try mod.addBuildingGLibCWorkItems();
     }
 
@@ -1273,6 +1359,15 @@ pub fn destroy(self: *Module) void {
     self.deletion_set.deinit(gpa);
     self.work_queue.deinit();
 
+    {
+        var it = self.crt_files.iterator();
+        while (it.next()) |entry| {
+            gpa.free(entry.key);
+            gpa.free(entry.value);
+        }
+        self.crt_files.deinit(gpa);
+    }
+
     for (self.decl_table.items()) |entry| {
         entry.value.destroy(gpa);
     }
@@ -1322,6 +1417,8 @@ pub fn destroy(self: *Module) void {
         gpa.free(entry.key);
     }
     self.global_error_set.deinit(gpa);
+
+    self.zig_cache_artifact_directory.handle.close();
     self.cache.release();
 
     // This destroys `self`.
@@ -1458,7 +1555,7 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors {
     if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) {
         const global_err_src_path = blk: {
             if (self.root_pkg) |root_pkg| break :blk root_pkg.root_src_path;
-            if (self.c_source_files.len != 0) break :blk self.c_source_files[0];
+            if (self.c_source_files.len != 0) break :blk self.c_source_files[0].src_path;
             if (self.bin_file.options.objects.len != 0) break :blk self.bin_file.options.objects[0];
             break :blk "(no file)";
         };
@@ -1581,6 +1678,17 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
                 },
             };
         },
+        .glibc_crt_file => |crt_file| {
+            glibc.buildCRTFile(self, crt_file) catch |err| {
+                // This is a problem with the Zig installation. It's mostly OK to crash here,
+                // but TODO because it would be even better if we could recover gracefully
+                // from temporary problems such as out-of-disk-space.
+                fatal("unable to build glibc CRT file: {}", .{@errorName(err)});
+            };
+        },
+        .glibc_so => |glibc_lib| {
+            fatal("TODO build glibc shared object '{}.so.{}'", .{ glibc_lib.name, glibc_lib.sover });
+        },
     };
 }
 
@@ -1588,6 +1696,8 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    // TODO this C source file needs its own cache hash instance
+
     if (!build_options.have_llvm) {
         return mod.failCObj(c_object, "clang not available: compiler not built with LLVM extensions enabled", .{});
     }
@@ -1616,6 +1726,9 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void {
     // We can't know the digest until we do the C compiler invocation, so we need a temporary filename.
     const out_obj_path = try mod.tmpFilePath(arena, o_basename);
 
+    var zig_cache_tmp_dir = try mod.zig_cache_directory.handle.makeOpenPath("tmp", .{});
+    defer zig_cache_tmp_dir.close();
+
     try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang", "-c" });
 
     const ext = classifyFileExt(c_object.src_path);
@@ -1628,9 +1741,12 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void {
     try argv.append(c_object.src_path);
     try argv.appendSlice(c_object.extra_flags);
 
-    //for (argv.items) |arg| {
-    //    std.debug.print("{} ", .{arg});
-    //}
+    if (mod.debug_cc) {
+        for (argv.items[0 .. argv.items.len - 1]) |arg| {
+            std.debug.print("{} ", .{arg});
+        }
+        std.debug.print("{}\n", .{argv.items[argv.items.len - 1]});
+    }
 
     const child = try std.ChildProcess.init(argv.items, arena);
     defer child.deinit();
@@ -1689,11 +1805,13 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void {
 
     // TODO handle .d files
 
-    // TODO rename into place
-    std.debug.print("TODO rename {} into cache dir\n", .{out_obj_path});
+    // TODO Add renameat capabilities to the std lib in a higher layer than the posix layer.
+    const tmp_basename = std.fs.path.basename(out_obj_path);
+    try std.os.renameat(zig_cache_tmp_dir.fd, tmp_basename, mod.zig_cache_artifact_directory.handle.fd, o_basename);
 
-    // TODO use the cache file name instead of tmp file name
-    const success_file_path = try mod.gpa.dupe(u8, out_obj_path);
+    const success_file_path = try std.fs.path.join(mod.gpa, &[_][]const u8{
+        mod.zig_cache_artifact_directory.path.?, o_basename,
+    });
     c_object.status = .{ .success = success_file_path };
 }
 
@@ -1702,7 +1820,7 @@ fn tmpFilePath(mod: *Module, arena: *Allocator, suffix: []const u8) error{OutOfM
     return std.fmt.allocPrint(
         arena,
         "{}" ++ s ++ "tmp" ++ s ++ "{x}-{}",
-        .{ mod.zig_cache_dir_path, mod.rand.int(u64), suffix },
+        .{ mod.zig_cache_directory.path.?, mod.rand.int(u64), suffix },
     );
 }
 
@@ -1749,10 +1867,10 @@ fn addCCArgs(
 
     if (mod.bin_file.options.link_libcpp) {
         const libcxx_include_path = try std.fs.path.join(arena, &[_][]const u8{
-            mod.zig_lib_dir, "libcxx", "include",
+            mod.zig_lib_directory.path.?, "libcxx", "include",
         });
         const libcxxabi_include_path = try std.fs.path.join(arena, &[_][]const u8{
-            mod.zig_lib_dir, "libcxxabi", "include",
+            mod.zig_lib_directory.path.?, "libcxxabi", "include",
         });
 
         try argv.append("-isystem");
@@ -1776,7 +1894,7 @@ fn addCCArgs(
             // According to Rich Felker libc headers are supposed to go before C language headers.
             // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics
             // and other compiler specific items.
-            const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ mod.zig_lib_dir, "include" });
+            const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, "include" });
             try argv.append("-isystem");
             try argv.append(c_headers_dir);
 
@@ -1891,10 +2009,9 @@ fn addCCArgs(
         },
     }
 
-    // TODO add CLI args for PIC
-    //if (target_supports_fpic(g->zig_target) and g->have_pic) {
-    //    try argv.append("-fPIC");
-    //}
+    if (target_util.supports_fpic(target) and mod.bin_file.options.pic) {
+        try argv.append("-fPIC");
+    }
 
     try argv.appendSlice(mod.clang_argv);
 }
@@ -4497,7 +4614,9 @@ fn detectLibCFromLibCInstallation(arena: *Allocator, target: Target, lci: *const
 }
 
 pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 {
-    // TODO port support for building crt files from stage1
+    if (mod.wantBuildGLibCFromSource()) {
+        return mod.crt_files.get(basename).?;
+    }
     const lci = mod.bin_file.options.libc_installation orelse return error.LibCInstallationNotAvailable;
     const crt_dir_path = lci.crt_dir orelse return error.LibCInstallationMissingCRTDir;
     const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename });
@@ -4505,6 +4624,23 @@ pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8)
 }
 
 fn addBuildingGLibCWorkItems(mod: *Module) !void {
-    // crti.o, crtn.o, start.os, abi-note.o, Scrt1.o, libc_nonshared.a
-    try mod.work_queue.ensureUnusedCapacity(6);
+    const static_file_work_items = [_]WorkItem{
+        .{ .glibc_crt_file = .crti_o },
+        .{ .glibc_crt_file = .crtn_o },
+        .{ .glibc_crt_file = .start_os },
+        .{ .glibc_crt_file = .abi_note_o },
+        .{ .glibc_crt_file = .scrt1_o },
+        .{ .glibc_crt_file = .libc_nonshared_a },
+    };
+    try mod.work_queue.ensureUnusedCapacity(static_file_work_items.len + glibc.libs.len);
+    mod.work_queue.writeAssumeCapacity(&static_file_work_items);
+    for (glibc.libs) |*glibc_so| {
+        mod.work_queue.writeItemAssumeCapacity(.{ .glibc_so = glibc_so });
+    }
+}
+
+fn wantBuildGLibCFromSource(mod: *Module) bool {
+    return mod.bin_file.options.link_libc and
+        mod.bin_file.options.libc_installation == null and
+        mod.bin_file.options.target.isGnuLibC();
 }
src-self-hosted/Package.zig
@@ -1,59 +1,59 @@
-pub const Table = std.StringHashMap(*Package);
+pub const Table = std.StringHashMapUnmanaged(*Package);
 
-/// This should be used for file operations.
-root_src_dir: std.fs.Dir,
-/// This is for metadata purposes, for example putting into debug information.
-root_src_dir_path: []u8,
-/// Relative to `root_src_dir` and `root_src_dir_path`.
+root_src_directory: Module.Directory,
+/// Relative to `root_src_directory`.
 root_src_path: []u8,
 table: Table,
 
 /// No references to `root_src_dir` and `root_src_path` are kept.
 pub fn create(
-    allocator: *mem.Allocator,
+    gpa: *Allocator,
     base_dir: std.fs.Dir,
     /// Relative to `base_dir`.
     root_src_dir: []const u8,
     /// Relative to `root_src_dir`.
     root_src_path: []const u8,
 ) !*Package {
-    const ptr = try allocator.create(Package);
-    errdefer allocator.destroy(ptr);
-    const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path);
-    errdefer allocator.free(root_src_path_dupe);
-    const root_src_dir_path = try mem.dupe(allocator, u8, root_src_dir);
-    errdefer allocator.free(root_src_dir_path);
+    const ptr = try gpa.create(Package);
+    errdefer gpa.destroy(ptr);
+    const root_src_path_dupe = try mem.dupe(gpa, u8, root_src_path);
+    errdefer gpa.free(root_src_path_dupe);
+    const root_src_dir_path = try mem.dupe(gpa, u8, root_src_dir);
+    errdefer gpa.free(root_src_dir_path);
     ptr.* = .{
-        .root_src_dir = try base_dir.openDir(root_src_dir, .{}),
-        .root_src_dir_path = root_src_dir_path,
+        .root_src_directory = .{
+            .path = root_src_dir_path,
+            .handle = try base_dir.openDir(root_src_dir, .{}),
+        },
         .root_src_path = root_src_path_dupe,
-        .table = Table.init(allocator),
+        .table = .{},
     };
     return ptr;
 }
 
-pub fn destroy(self: *Package) void {
-    const allocator = self.table.allocator;
-    self.root_src_dir.close();
-    allocator.free(self.root_src_path);
-    allocator.free(self.root_src_dir_path);
+pub fn destroy(pkg: *Package, gpa: *Allocator) void {
+    pkg.root_src_directory.handle.close();
+    gpa.free(pkg.root_src_path);
+    if (pkg.root_src_directory.path) |p| gpa.free(p);
     {
-        var it = self.table.iterator();
+        var it = pkg.table.iterator();
         while (it.next()) |kv| {
-            allocator.free(kv.key);
+            gpa.free(kv.key);
         }
     }
-    self.table.deinit();
-    allocator.destroy(self);
+    pkg.table.deinit(gpa);
+    gpa.destroy(pkg);
 }
 
-pub fn add(self: *Package, name: []const u8, package: *Package) !void {
-    try self.table.ensureCapacity(self.table.items().len + 1);
-    const name_dupe = try mem.dupe(self.table.allocator, u8, name);
-    self.table.putAssumeCapacityNoClobber(name_dupe, package);
+pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void {
+    try pkg.table.ensureCapacity(gpa, pkg.table.items().len + 1);
+    const name_dupe = try mem.dupe(gpa, u8, name);
+    pkg.table.putAssumeCapacityNoClobber(name_dupe, package);
 }
 
 const std = @import("std");
 const mem = std.mem;
+const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
 const Package = @This();
+const Module = @import("Module.zig");
src-self-hosted/print_env.zig
@@ -2,15 +2,19 @@ const std = @import("std");
 const build_options = @import("build_options");
 const introspect = @import("introspect.zig");
 const Allocator = std.mem.Allocator;
+const fatal = @import("main.zig").fatal;
 
 pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: anytype) !void {
-    const zig_lib_dir = introspect.resolveZigLibDir(gpa) catch |err| {
-        std.debug.print("unable to find zig installation directory: {}\n", .{@errorName(err)});
-        std.process.exit(1);
+    const self_exe_path = try std.fs.selfExePathAlloc(gpa);
+    defer gpa.free(self_exe_path);
+
+    var zig_lib_directory = introspect.findZigLibDirFromSelfExe(gpa, self_exe_path) catch |err| {
+        fatal("unable to find zig installation directory: {}\n", .{@errorName(err)});
     };
-    defer gpa.free(zig_lib_dir);
+    defer gpa.free(zig_lib_directory.path.?);
+    defer zig_lib_directory.handle.close();
 
-    const zig_std_dir = try std.fs.path.join(gpa, &[_][]const u8{ zig_lib_dir, "std" });
+    const zig_std_dir = try std.fs.path.join(gpa, &[_][]const u8{ zig_lib_directory.path.?, "std" });
     defer gpa.free(zig_std_dir);
 
     const global_cache_dir = try introspect.resolveGlobalCacheDir(gpa);
@@ -22,8 +26,11 @@ pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: anytype) !void
     var jws = std.json.WriteStream(@TypeOf(bos_stream), 6).init(bos_stream);
     try jws.beginObject();
 
+    try jws.objectField("zig_exe");
+    try jws.emitString(self_exe_path);
+
     try jws.objectField("lib_dir");
-    try jws.emitString(zig_lib_dir);
+    try jws.emitString(zig_lib_directory.path.?);
 
     try jws.objectField("std_dir");
     try jws.emitString(zig_std_dir);
src-self-hosted/print_targets.zig
@@ -17,16 +17,14 @@ pub fn cmdTargets(
     stdout: anytype,
     native_target: Target,
 ) !void {
-    const zig_lib_dir_path = introspect.resolveZigLibDir(allocator) catch |err| {
+    var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| {
         fatal("unable to find zig installation directory: {}\n", .{@errorName(err)});
     };
-    defer allocator.free(zig_lib_dir_path);
+    defer zig_lib_directory.handle.close();
+    defer allocator.free(zig_lib_directory.path.?);
 
-    var zig_lib_dir = try fs.cwd().openDir(zig_lib_dir_path, .{});
-    defer zig_lib_dir.close();
-
-    const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_dir);
-    errdefer glibc_abi.destroy(allocator);
+    const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_directory.handle);
+    defer glibc_abi.destroy(allocator);
 
     var bos = io.bufferedOutStream(stdout);
     const bos_stream = bos.outStream();
src-self-hosted/target.zig
@@ -117,10 +117,10 @@ pub fn cannotDynamicLink(target: std.Target) bool {
     };
 }
 
+/// On Darwin, we always link libSystem which contains libc.
+/// Similarly on FreeBSD and NetBSD we always link system libc
+/// since this is the stable syscall interface.
 pub fn osRequiresLibC(target: std.Target) bool {
-    // On Darwin, we always link libSystem which contains libc.
-    // Similarly on FreeBSD and NetBSD we always link system libc
-    // since this is the stable syscall interface.
     return switch (target.os.tag) {
         .freebsd, .netbsd, .dragonfly, .macosx, .ios, .watchos, .tvos => true,
         else => false,
@@ -131,6 +131,40 @@ pub fn requiresPIE(target: std.Target) bool {
     return target.isAndroid();
 }
 
+/// This function returns whether non-pic code is completely invalid on the given target.
+pub fn requiresPIC(target: std.Target, linking_libc: bool) bool {
+    return target.isAndroid() or
+        target.os.tag == .windows or target.os.tag == .uefi or
+        osRequiresLibC(target) or
+        (linking_libc and target.isGnuLibC());
+}
+
+/// This is not whether the target supports Position Independent Code, but whether the -fPIC
+/// C compiler argument is valid to Clang.
+pub fn supports_fpic(target: std.Target) bool {
+    return target.os.tag != .windows;
+}
+
 pub fn libc_needs_crti_crtn(target: std.Target) bool {
     return !(target.cpu.arch.isRISCV() or target.isAndroid());
 }
+
+pub fn isSingleThreaded(target: std.Target) bool {
+    return target.isWasm();
+}
+
+/// Valgrind supports more, but Zig does not support them yet.
+pub fn hasValgrindSupport(target: std.Target) bool {
+    switch (target.cpu.arch) {
+        .x86_64 => {
+            return target.os.tag == .linux or target.isDarwin() or target.os.tag == .solaris or
+                (target.os.tag == .windows and target.abi != .msvc);
+        },
+        else => return false,
+    }
+}
+
+pub fn supportsStackProbing(target: std.Target) bool {
+    return target.os.tag != .windows and target.os.tag != .uefi and
+        (target.cpu.arch == .i386 or target.cpu.arch == .x86_64);
+}
src-self-hosted/test.zig
@@ -407,8 +407,9 @@ pub const TestContext = struct {
         const root_node = try progress.start("tests", self.cases.items.len);
         defer root_node.end();
 
-        const zig_lib_dir = try introspect.resolveZigLibDir(std.testing.allocator);
-        defer std.testing.allocator.free(zig_lib_dir);
+        var zig_lib_directory = try introspect.findZigLibDir(std.testing.allocator);
+        defer zig_lib_directory.handle.close();
+        defer std.testing.allocator.free(zig_lib_directory.path.?);
 
         const random_seed = blk: {
             var random_seed: u64 = undefined;
@@ -427,7 +428,7 @@ pub const TestContext = struct {
             progress.initial_delay_ns = 0;
             progress.refresh_rate_ns = 0;
 
-            try self.runOneCase(std.testing.allocator, &prg_node, case, zig_lib_dir, &default_prng.random);
+            try self.runOneCase(std.testing.allocator, &prg_node, case, zig_lib_directory, &default_prng.random);
         }
     }
 
@@ -436,7 +437,7 @@ pub const TestContext = struct {
         allocator: *Allocator,
         root_node: *std.Progress.Node,
         case: Case,
-        zig_lib_dir: []const u8,
+        zig_lib_directory: Module.Directory,
         rand: *std.rand.Random,
     ) !void {
         const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target);
@@ -449,15 +450,32 @@ pub const TestContext = struct {
         var tmp = std.testing.tmpDir(.{});
         defer tmp.cleanup();
 
+        var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
+        defer cache_dir.close();
+        const bogus_path = "bogus"; // TODO this will need to be fixed before we can test LLVM extensions
+        const zig_cache_directory: Module.Directory = .{
+            .handle = cache_dir,
+            .path = try std.fs.path.join(arena, &[_][]const u8{ bogus_path, "zig-cache" }),
+        };
+
         const tmp_src_path = if (case.extension == .Zig) "test_case.zig" else if (case.extension == .ZIR) "test_case.zir" else unreachable;
         const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
-        defer root_pkg.destroy();
+        defer root_pkg.destroy(allocator);
 
         const ofmt: ?std.builtin.ObjectFormat = if (case.cbe) .c else null;
         const bin_name = try std.zig.binNameAlloc(arena, "test_case", target, case.output_mode, null, ofmt);
 
+        const emit_directory: Module.Directory = .{
+            .path = bogus_path,
+            .handle = tmp.dir,
+        };
+        const emit_bin: Module.EmitLoc = .{
+            .directory = emit_directory,
+            .basename = bin_name,
+        };
         const module = try Module.create(allocator, .{
-            .zig_lib_dir = zig_lib_dir,
+            .zig_cache_directory = zig_cache_directory,
+            .zig_lib_directory = zig_lib_directory,
             .rand = rand,
             .root_name = "test_case",
             .target = target,
@@ -467,8 +485,7 @@ pub const TestContext = struct {
             .output_mode = case.output_mode,
             // TODO: support testing optimizations
             .optimize_mode = .Debug,
-            .bin_file_dir = tmp.dir,
-            .bin_file_path = bin_name,
+            .emit_bin = emit_bin,
             .root_pkg = root_pkg,
             .keep_source_files_loaded = true,
             .object_format = ofmt,