Commit 4b02a39aa9

Andrew Kelley <andrew@ziglang.org>
2020-02-16 19:25:30
self-hosted libc detection
* libc_installation.cpp is deleted. src-self-hosted/libc_installation.zig is now used for both stage1 and stage2 compilers. * (breaking) move `std.fs.File.access` to `std.fs.Dir.access`. The API now encourages use with an open directory handle. * Add `std.os.faccessat` and related functions. * Deprecate the "C" suffix naming convention for null-terminated parameters. "C" should be used when it is related to libc. However null-terminated parameters often have to do with the native system ABI rather than libc. "Z" suffix is the new convention. For example, `std.os.openC` is deprecated in favor of `std.os.openZ`. * Add `std.mem.dupeZ` for using an allocator to copy memory and add a null terminator. * Remove dead struct field `std.ChildProcess.llnode`. * Introduce `std.event.Batch`. This API allows expressing concurrency without forcing code to be async. It requires no Allocator and does not introduce any failure conditions. However it is not thread-safe. * There is now an ongoing experiment to transition away from `std.event.Group` in favor of `std.event.Batch`. * `std.os.execvpeC` calls `getenvZ` rather than `getenv`. This is slightly more efficient on most systems, and works around a limitation of `getenv` lack of integration with libc. * (breaking) `std.os.AccessError` gains `FileBusy`, `SymLinkLoop`, and `ReadOnlyFileSystem`. Previously these error codes were all reported as `PermissionDenied`. * Add `std.Target.isDragonFlyBSD`. * stage2: access to the windows_sdk functions is done with a manually maintained .zig binding file instead of `@cImport`. * Update src-self-hosted/libc_installation.zig with all the improvements that stage1 has seen to src/libc_installation.cpp until now. In addition, it now takes advantage of Batch so that evented I/O mode takes advantage of concurrency, but it still works in blocking I/O mode, which is how it is used in stage1.
1 parent 5e37fc0
lib/std/event/batch.zig
@@ -0,0 +1,139 @@
+const std = @import("../std.zig");
+const testing = std.testing;
+
+/// Performs multiple async functions in parallel, without heap allocation.
+/// Async function frames are managed externally to this abstraction, and
+/// passed in via the `add` function. Once all the jobs are added, call `wait`.
+/// This API is *not* thread-safe. The object must be accessed from one thread at
+/// a time, however, it need not be the same thread.
+pub fn Batch(
+    /// The return value for each job.
+    /// If a job slot was re-used due to maxed out concurrency, then its result
+    /// value will be overwritten. The values can be accessed with the `results` field.
+    comptime Result: type,
+    /// How many jobs to run in parallel.
+    comptime max_jobs: comptime_int,
+    /// Controls whether the `add` and `wait` functions will be async functions.
+    comptime async_behavior: enum {
+        /// Observe the value of `std.io.is_async` to decide whether `add`
+        /// and `wait` will be async functions. Asserts that the jobs do not suspend when
+        /// `std.io.mode == .blocking`. This is a generally safe assumption, and the
+        /// usual recommended option for this parameter.
+        auto_async,
+
+        /// Always uses the `noasync` keyword when using `await` on the jobs,
+        /// making `add` and `wait` non-async functions. Asserts that the jobs do not suspend.
+        never_async,
+
+        /// `add` and `wait` use regular `await` keyword, making them async functions.
+        always_async,
+    },
+) type {
+    return struct {
+        jobs: [max_jobs]Job,
+        next_job_index: usize,
+        collected_result: CollectedResult,
+
+        const Job = struct {
+            frame: ?anyframe->Result,
+            result: Result,
+        };
+
+        const Self = @This();
+
+        const CollectedResult = switch (@typeInfo(Result)) {
+            .ErrorUnion => Result,
+            else => void,
+        };
+
+        const async_ok = switch (async_behavior) {
+            .auto_async => std.io.is_async,
+            .never_async => false,
+            .always_async => true,
+        };
+
+        pub fn init() Self {
+            return Self{
+                .jobs = [1]Job{
+                    .{
+                        .frame = null,
+                        .result = undefined,
+                    },
+                } ** max_jobs,
+                .next_job_index = 0,
+                .collected_result = {},
+            };
+        }
+
+        /// Add a frame to the Batch. If all jobs are in-flight, then this function
+        /// waits until one completes.
+        /// This function is *not* thread-safe. It must be called from one thread at
+        /// a time, however, it need not be the same thread.
+        /// TODO: "select" language feature to use the next available slot, rather than
+        /// awaiting the next index.
+        pub fn add(self: *Self, frame: anyframe->Result) void {
+            const job = &self.jobs[self.next_job_index];
+            self.next_job_index = (self.next_job_index + 1) % max_jobs;
+            if (job.frame) |existing| {
+                job.result = if (async_ok) await existing else noasync await existing;
+                if (CollectedResult != void) {
+                    job.result catch |err| {
+                        self.collected_result = err;
+                    };
+                }
+            }
+            job.frame = frame;
+        }
+
+        /// Wait for all the jobs to complete.
+        /// Safe to call any number of times.
+        /// If `Result` is an error union, this function returns the last error that occurred, if any.
+        /// Unlike the `results` field, the return value of `wait` will report any error that occurred;
+        /// hitting max parallelism will not compromise the result.
+        /// This function is *not* thread-safe. It must be called from one thread at
+        /// a time, however, it need not be the same thread.
+        pub fn wait(self: *Self) CollectedResult {
+            for (self.jobs) |*job| if (job.frame) |f| {
+                job.result = if (async_ok) await f else noasync await f;
+                if (CollectedResult != void) {
+                    job.result catch |err| {
+                        self.collected_result = err;
+                    };
+                }
+                job.frame = null;
+            };
+            return self.collected_result;
+        }
+    };
+}
+
+test "std.event.Batch" {
+    var count: usize = 0;
+    var batch = Batch(void, 2).init();
+    batch.add(&async sleepALittle(&count));
+    batch.add(&async increaseByTen(&count));
+    batch.wait();
+    testing.expect(count == 11);
+
+    var another = Batch(anyerror!void, 2).init();
+    another.add(&async somethingElse());
+    another.add(&async doSomethingThatFails());
+    testing.expectError(error.ItBroke, another.wait());
+}
+
+fn sleepALittle(count: *usize) void {
+    std.time.sleep(1 * std.time.millisecond);
+    _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
+}
+
+fn increaseByTen(count: *usize) void {
+    var i: usize = 0;
+    while (i < 10) : (i += 1) {
+        _ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
+    }
+}
+
+fn doSomethingThatFails() anyerror!void {}
+fn somethingElse() anyerror!void {
+    return error.ItBroke;
+}
lib/std/event/group.zig
@@ -5,6 +5,11 @@ const testing = std.testing;
 const Allocator = std.mem.Allocator;
 
 /// ReturnType must be `void` or `E!void`
+/// TODO This API was created back with the old design of async/await, when calling any
+/// async function required an allocator. There is an ongoing experiment to transition
+/// all uses of this API to the simpler and more resource-aware `std.event.Batch` API.
+/// If the transition goes well, all usages of `Group` will be gone, and this API
+/// will be deleted.
 pub fn Group(comptime ReturnType: type) type {
     return struct {
         frame_stack: Stack,
lib/std/fs/file.zig
@@ -60,31 +60,6 @@ pub const File = struct {
         mode: Mode = default_mode,
     };
 
-    /// Test for the existence of `path`.
-    /// `path` is UTF8-encoded.
-    /// In general it is recommended to avoid this function. For example,
-    /// instead of testing if a file exists and then opening it, just
-    /// open it and handle the error for file not found.
-    /// TODO: deprecate this and move it to `std.fs.Dir`.
-    /// TODO: integrate with async I/O
-    pub fn access(path: []const u8) !void {
-        return os.access(path, os.F_OK);
-    }
-
-    /// Same as `access` except the parameter is null-terminated.
-    /// TODO: deprecate this and move it to `std.fs.Dir`.
-    /// TODO: integrate with async I/O
-    pub fn accessC(path: [*:0]const u8) !void {
-        return os.accessC(path, os.F_OK);
-    }
-
-    /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
-    /// TODO: deprecate this and move it to `std.fs.Dir`.
-    /// TODO: integrate with async I/O
-    pub fn accessW(path: [*:0]const u16) !void {
-        return os.accessW(path, os.F_OK);
-    }
-
     /// Upon success, the stream is in an uninitialized state. To continue using it,
     /// you must use the open() function.
     pub fn close(self: File) void {
lib/std/os/test.zig
@@ -29,7 +29,7 @@ test "makePath, put some files in it, deleteTree" {
 
 test "access file" {
     try fs.makePath(a, "os_test_tmp");
-    if (File.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt")) |ok| {
+    if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
         @panic("expected error");
     } else |err| {
         expect(err == error.FileNotFound);
lib/std/c.zig
@@ -96,6 +96,7 @@ pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
 pub extern "c" fn waitpid(pid: c_int, stat_loc: *c_uint, options: c_uint) c_int;
 pub extern "c" fn fork() c_int;
 pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int;
+pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int;
 pub extern "c" fn pipe(fds: *[2]fd_t) c_int;
 pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;
 pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
lib/std/child_process.zig
@@ -48,7 +48,6 @@ pub const ChildProcess = struct {
     cwd: ?[]const u8,
 
     err_pipe: if (builtin.os == .windows) void else [2]os.fd_t,
-    llnode: if (builtin.os == .windows) void else TailQueue(*ChildProcess).Node,
 
     pub const SpawnError = error{
         OutOfMemory,
@@ -90,7 +89,6 @@ pub const ChildProcess = struct {
             .handle = undefined,
             .thread_handle = undefined,
             .err_pipe = undefined,
-            .llnode = undefined,
             .term = null,
             .env_map = null,
             .cwd = null,
@@ -453,7 +451,6 @@ pub const ChildProcess = struct {
 
         self.pid = pid;
         self.err_pipe = err_pipe;
-        self.llnode = TailQueue(*ChildProcess).Node.init(self);
         self.term = null;
 
         if (self.stdin_behavior == StdIo.Pipe) {
lib/std/event.zig
@@ -1,6 +1,7 @@
 pub const Channel = @import("event/channel.zig").Channel;
 pub const Future = @import("event/future.zig").Future;
 pub const Group = @import("event/group.zig").Group;
+pub const Batch = @import("event/batch.zig").Batch;
 pub const Lock = @import("event/lock.zig").Lock;
 pub const Locked = @import("event/locked.zig").Locked;
 pub const RwLock = @import("event/rwlock.zig").RwLock;
@@ -11,6 +12,7 @@ test "import event tests" {
     _ = @import("event/channel.zig");
     _ = @import("event/future.zig");
     _ = @import("event/group.zig");
+    _ = @import("event/batch.zig");
     _ = @import("event/lock.zig");
     _ = @import("event/locked.zig");
     _ = @import("event/rwlock.zig");
lib/std/fs.zig
@@ -1323,6 +1323,38 @@ pub const Dir = struct {
         defer file.close();
         try file.write(data);
     }
+
+    pub const AccessError = os.AccessError;
+
+    /// Test accessing `path`.
+    /// `path` is UTF8-encoded.
+    /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
+    /// For example, instead of testing if a file exists and then opening it, just
+    /// open it and handle the error for file not found.
+    pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
+        const path_c = try os.toPosixPath(sub_path);
+        return self.accessZ(&path_c, flags);
+    }
+
+    /// Same as `access` except the path parameter is null-terminated.
+    pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
+        const os_mode = if (flags.write and flags.read)
+            @as(u32, os.R_OK | os.W_OK)
+        else if (flags.write)
+            @as(u32, os.W_OK)
+        else
+            @as(u32, os.F_OK);
+        const result = if (need_async_thread)
+            std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode)
+        else
+            os.faccessatZ(self.fd, sub_path, os_mode, 0);
+        return result;
+    }
+
+    /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
+    pub fn accessW(self: Dir, sub_path: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
+        return os.faccessatW(self.fd, sub_path, 0, 0);
+    }
 };
 
 /// Returns an handle to the current working directory that is open for traversal.
lib/std/mem.zig
@@ -387,13 +387,21 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
     return true;
 }
 
-/// Copies ::m to newly allocated memory. Caller is responsible to free it.
+/// Copies `m` to newly allocated memory. Caller owns the memory.
 pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
     const new_buf = try allocator.alloc(T, m.len);
     copy(T, new_buf, m);
     return new_buf;
 }
 
+/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
+pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
+    const new_buf = try allocator.alloc(T, m.len + 1);
+    copy(T, new_buf, m);
+    new_buf[m.len] = 0;
+    return new_buf[0..m.len :0];
+}
+
 /// Remove values from the beginning of a slice.
 pub fn trimLeft(comptime T: type, slice: []const T, values_to_strip: []const T) []const T {
     var begin: usize = 0;
lib/std/os.zig
@@ -950,7 +950,7 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e
     const file_slice = mem.toSliceConst(u8, file);
     if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp);
 
-    const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
+    const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
     var path_buf: [MAX_PATH_BYTES]u8 = undefined;
     var it = mem.tokenize(PATH, ":");
     var seen_eacces = false;
@@ -1038,7 +1038,7 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8)
 }
 
 /// Get an environment variable.
-/// See also `getenvC`.
+/// See also `getenvZ`.
 /// TODO make this go through libc when we have it
 pub fn getenv(key: []const u8) ?[]const u8 {
     for (environ) |ptr| {
@@ -1056,9 +1056,12 @@ pub fn getenv(key: []const u8) ?[]const u8 {
     return null;
 }
 
+/// Deprecated in favor of `getenvZ`.
+pub const getenvC = getenvZ;
+
 /// Get an environment variable with a null-terminated name.
 /// See also `getenv`.
-pub fn getenvC(key: [*:0]const u8) ?[]const u8 {
+pub fn getenvZ(key: [*:0]const u8) ?[]const u8 {
     if (builtin.link_libc) {
         const value = system.getenv(key) orelse return null;
         return mem.toSliceConst(u8, value);
@@ -2452,6 +2455,9 @@ pub const AccessError = error{
     InputOutput,
     SystemResources,
     BadPathName,
+    FileBusy,
+    SymLinkLoop,
+    ReadOnlyFileSystem,
 
     /// On Windows, file paths must be valid Unicode.
     InvalidUtf8,
@@ -2469,8 +2475,11 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
     return accessC(&path_c, mode);
 }
 
+/// Deprecated in favor of `accessZ`.
+pub const accessC = accessZ;
+
 /// Same as `access` except `path` is null-terminated.
-pub fn accessC(path: [*:0]const u8, mode: u32) AccessError!void {
+pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
     if (builtin.os == .windows) {
         const path_w = try windows.cStrToPrefixedFileW(path);
         _ = try windows.GetFileAttributesW(&path_w);
@@ -2479,12 +2488,11 @@ pub fn accessC(path: [*:0]const u8, mode: u32) AccessError!void {
     switch (errno(system.access(path, mode))) {
         0 => return,
         EACCES => return error.PermissionDenied,
-        EROFS => return error.PermissionDenied,
-        ELOOP => return error.PermissionDenied,
-        ETXTBSY => return error.PermissionDenied,
+        EROFS => return error.ReadOnlyFileSystem,
+        ELOOP => return error.SymLinkLoop,
+        ETXTBSY => return error.FileBusy,
         ENOTDIR => return error.FileNotFound,
         ENOENT => return error.FileNotFound,
-
         ENAMETOOLONG => return error.NameTooLong,
         EINVAL => unreachable,
         EFAULT => unreachable,
@@ -2510,6 +2518,47 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v
     }
 }
 
+/// Check user's permissions for a file, based on an open directory handle.
+/// TODO currently this ignores `mode` and `flags` on Windows.
+pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
+    if (builtin.os == .windows) {
+        const path_w = try windows.sliceToPrefixedFileW(path);
+        return faccessatW(dirfd, &path_w, mode, flags);
+    }
+    const path_c = try toPosixPath(path);
+    return faccessatZ(dirfd, &path_c, mode, flags);
+}
+
+/// Same as `faccessat` except the path parameter is null-terminated.
+pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
+    if (builtin.os == .windows) {
+        const path_w = try windows.cStrToPrefixedFileW(path);
+        return faccessatW(dirfd, &path_w, mode, flags);
+    }
+    switch (errno(system.faccessat(dirfd, path, mode, flags))) {
+        0 => return,
+        EACCES => return error.PermissionDenied,
+        EROFS => return error.ReadOnlyFileSystem,
+        ELOOP => return error.SymLinkLoop,
+        ETXTBSY => return error.FileBusy,
+        ENOTDIR => return error.FileNotFound,
+        ENOENT => return error.FileNotFound,
+        ENAMETOOLONG => return error.NameTooLong,
+        EINVAL => unreachable,
+        EFAULT => unreachable,
+        EIO => return error.InputOutput,
+        ENOMEM => return error.SystemResources,
+        else => |err| return unexpectedErrno(err),
+    }
+}
+
+/// Same as `faccessat` except asserts the target is Windows and the path parameter
+/// is null-terminated WTF-16 encoded.
+/// TODO currently this ignores `mode` and `flags`
+pub fn faccessatW(dirfd: fd_t, path: [*:0]const u16, mode: u32, flags: u32) AccessError!void {
+    @compileError("TODO implement faccessatW on Windows");
+}
+
 pub const PipeError = error{
     SystemFdQuotaExceeded,
     ProcessFdQuotaExceeded,
lib/std/target.zig
@@ -1037,6 +1037,13 @@ pub const Target = union(enum) {
         };
     }
 
+    pub fn isDragonFlyBSD(self: Target) bool {
+        return switch (self.getOs()) {
+            .dragonfly => true,
+            else => false,
+        };
+    }
+
     pub fn isUefi(self: Target) bool {
         return switch (self.getOs()) {
             .uefi => true,
lib/std/time.zig
@@ -8,6 +8,7 @@ const math = std.math;
 pub const epoch = @import("time/epoch.zig");
 
 /// Spurious wakeups are possible and no precision of timing is guaranteed.
+/// TODO integrate with evented I/O
 pub fn sleep(nanoseconds: u64) void {
     if (builtin.os == .windows) {
         const ns_per_ms = ns_per_s / ms_per_s;
src/all_types.hpp
@@ -18,7 +18,6 @@
 #include "bigfloat.hpp"
 #include "target.hpp"
 #include "tokenizer.hpp"
-#include "libc_installation.hpp"
 
 struct AstNode;
 struct ZigFn;
@@ -2139,7 +2138,7 @@ struct CodeGen {
     // As an input parameter, mutually exclusive with enable_cache. But it gets
     // populated in codegen_build_and_link.
     Buf *output_dir;
-    Buf **libc_include_dir_list;
+    const char **libc_include_dir_list;
     size_t libc_include_dir_len;
 
     Buf *zig_c_headers_dir; // Cannot be overridden; derived from zig_lib_dir.
@@ -2220,7 +2219,7 @@ struct CodeGen {
     ZigList<const char *> lib_dirs;
     ZigList<const char *> framework_dirs;
 
-    ZigLibCInstallation *libc;
+    Stage2LibCInstallation *libc;
 
     size_t version_major;
     size_t version_minor;
src/codegen.cpp
@@ -8982,17 +8982,20 @@ static void detect_dynamic_linker(CodeGen *g) {
 #if defined(ZIG_OS_LINUX)
         {
             Error err;
-            Buf *result = buf_alloc();
             for (size_t i = 0; possible_ld_names[i] != NULL; i += 1) {
                 const char *lib_name = possible_ld_names[i];
-                if ((err = zig_libc_cc_print_file_name(lib_name, result, false, true))) {
+                char *result_ptr;
+                size_t result_len;
+                if ((err = stage2_libc_cc_print_file_name(&result_ptr, &result_len, lib_name, false))) {
                     if (err != ErrorCCompilerCannotFindFile && err != ErrorNoCCompilerInstalled) {
                         fprintf(stderr, "Unable to detect native dynamic linker: %s\n", err_str(err));
                         exit(1);
                     }
                     continue;
                 }
-                g->dynamic_linker_path = result;
+                g->dynamic_linker_path = buf_create_from_mem(result_ptr, result_len);
+                // Skips heap::c_allocator because the memory is allocated by stage2 library.
+                free(result_ptr);
                 return;
             }
         }
@@ -9028,16 +9031,16 @@ static void detect_libc(CodeGen *g) {
                 buf_ptr(g->zig_lib_dir), target_os_name(g->zig_target->os));
 
         g->libc_include_dir_len = 4;
-        g->libc_include_dir_list = heap::c_allocator.allocate<Buf*>(g->libc_include_dir_len);
-        g->libc_include_dir_list[0] = arch_include_dir;
-        g->libc_include_dir_list[1] = generic_include_dir;
-        g->libc_include_dir_list[2] = arch_os_include_dir;
-        g->libc_include_dir_list[3] = generic_os_include_dir;
+        g->libc_include_dir_list = heap::c_allocator.allocate<const char*>(g->libc_include_dir_len);
+        g->libc_include_dir_list[0] = buf_ptr(arch_include_dir);
+        g->libc_include_dir_list[1] = buf_ptr(generic_include_dir);
+        g->libc_include_dir_list[2] = buf_ptr(arch_os_include_dir);
+        g->libc_include_dir_list[3] = buf_ptr(generic_os_include_dir);
         return;
     }
 
     if (g->zig_target->is_native) {
-        g->libc = heap::c_allocator.create<ZigLibCInstallation>();
+        g->libc = heap::c_allocator.create<Stage2LibCInstallation>();
 
         // search for native_libc.txt in following dirs:
         //   - LOCAL_CACHE_DIR
@@ -9082,8 +9085,8 @@ static void detect_libc(CodeGen *g) {
         if (libc_txt == nullptr)
             libc_txt = &global_libc_txt;
 
-        if ((err = zig_libc_parse(g->libc, libc_txt, g->zig_target, false))) {
-            if ((err = zig_libc_find_native(g->libc, true))) {
+        if ((err = stage2_libc_parse(g->libc, buf_ptr(libc_txt)))) {
+            if ((err = stage2_libc_find_native(g->libc))) {
                 fprintf(stderr,
                     "Unable to link against libc: Unable to find libc installation: %s\n"
                     "See `zig libc --help` for more details.\n", err_str(err));
@@ -9103,7 +9106,7 @@ static void detect_libc(CodeGen *g) {
                 fprintf(stderr, "Unable to open %s: %s\n", buf_ptr(native_libc_tmp), strerror(errno));
                 exit(1);
             }
-            zig_libc_render(g->libc, file);
+            stage2_libc_render(g->libc, file);
             if (fclose(file) != 0) {
                 fprintf(stderr, "Unable to save %s: %s\n", buf_ptr(native_libc_tmp), strerror(errno));
                 exit(1);
@@ -9113,27 +9116,28 @@ static void detect_libc(CodeGen *g) {
                 exit(1);
             }
         }
-        bool want_sys_dir = !buf_eql_buf(&g->libc->include_dir, &g->libc->sys_include_dir);
+        bool want_sys_dir = !mem_eql_mem(g->libc->include_dir,     g->libc->include_dir_len,
+                                         g->libc->sys_include_dir, g->libc->sys_include_dir_len);
         size_t want_um_and_shared_dirs = (g->zig_target->os == OsWindows) ? 2 : 0;
         size_t dir_count = 1 + want_sys_dir + want_um_and_shared_dirs;
         g->libc_include_dir_len = 0;
-        g->libc_include_dir_list = heap::c_allocator.allocate<Buf*>(dir_count);
+        g->libc_include_dir_list = heap::c_allocator.allocate<const char *>(dir_count);
 
-        g->libc_include_dir_list[g->libc_include_dir_len] = &g->libc->include_dir;
+        g->libc_include_dir_list[g->libc_include_dir_len] = g->libc->include_dir;
         g->libc_include_dir_len += 1;
 
         if (want_sys_dir) {
-            g->libc_include_dir_list[g->libc_include_dir_len] = &g->libc->sys_include_dir;
+            g->libc_include_dir_list[g->libc_include_dir_len] = g->libc->sys_include_dir;
             g->libc_include_dir_len += 1;
         }
 
         if (want_um_and_shared_dirs != 0) {
-            g->libc_include_dir_list[g->libc_include_dir_len] = buf_sprintf("%s" OS_SEP ".." OS_SEP "um",
-                    buf_ptr(&g->libc->include_dir));
+            g->libc_include_dir_list[g->libc_include_dir_len] = buf_ptr(buf_sprintf(
+                        "%s" OS_SEP ".." OS_SEP "um", g->libc->include_dir));
             g->libc_include_dir_len += 1;
 
-            g->libc_include_dir_list[g->libc_include_dir_len] = buf_sprintf("%s" OS_SEP ".." OS_SEP "shared",
-                    buf_ptr(&g->libc->include_dir));
+            g->libc_include_dir_list[g->libc_include_dir_len] = buf_ptr(buf_sprintf(
+                        "%s" OS_SEP ".." OS_SEP "shared", g->libc->include_dir));
             g->libc_include_dir_len += 1;
         }
         assert(g->libc_include_dir_len == dir_count);
@@ -9208,9 +9212,9 @@ void add_cc_args(CodeGen *g, ZigList<const char *> &args, const char *out_dep_pa
     args.append(buf_ptr(g->zig_c_headers_dir));
 
     for (size_t i = 0; i < g->libc_include_dir_len; i += 1) {
-        Buf *include_dir = g->libc_include_dir_list[i];
+        const char *include_dir = g->libc_include_dir_list[i];
         args.append("-isystem");
-        args.append(buf_ptr(include_dir));
+        args.append(include_dir);
     }
 
     if (g->zig_target->is_native) {
@@ -9666,7 +9670,7 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose
     cache_buf(cache_hash, compiler_id);
     cache_int(cache_hash, g->err_color);
     cache_buf(cache_hash, g->zig_c_headers_dir);
-    cache_list_of_buf(cache_hash, g->libc_include_dir_list, g->libc_include_dir_len);
+    cache_list_of_str(cache_hash, g->libc_include_dir_list, g->libc_include_dir_len);
     cache_int(cache_hash, g->zig_target->is_native);
     cache_int(cache_hash, g->zig_target->arch);
     cache_int(cache_hash, g->zig_target->sub_arch);
@@ -10482,11 +10486,11 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
     cache_list_of_str(ch, g->lib_dirs.items, g->lib_dirs.length);
     cache_list_of_str(ch, g->framework_dirs.items, g->framework_dirs.length);
     if (g->libc) {
-        cache_buf(ch, &g->libc->include_dir);
-        cache_buf(ch, &g->libc->sys_include_dir);
-        cache_buf(ch, &g->libc->crt_dir);
-        cache_buf(ch, &g->libc->msvc_lib_dir);
-        cache_buf(ch, &g->libc->kernel32_lib_dir);
+        cache_str(ch, g->libc->include_dir);
+        cache_str(ch, g->libc->sys_include_dir);
+        cache_str(ch, g->libc->crt_dir);
+        cache_str(ch, g->libc->msvc_lib_dir);
+        cache_str(ch, g->libc->kernel32_lib_dir);
     }
     cache_buf_opt(ch, g->dynamic_linker_path);
     cache_buf_opt(ch, g->version_script_path);
@@ -10765,7 +10769,7 @@ ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const c
 }
 
 CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
-        ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *parent_progress_node)
+        Stage2LibCInstallation *libc, const char *name, Stage2ProgressNode *parent_progress_node)
 {
     Stage2ProgressNode *child_progress_node = stage2_progress_start(
             parent_progress_node ? parent_progress_node : parent_gen->sub_progress_node,
@@ -10804,7 +10808,7 @@ CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType o
 
 CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget *target,
     OutType out_type, BuildMode build_mode, Buf *override_lib_dir,
-    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node)
+    Stage2LibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node)
 {
     CodeGen *g = heap::c_allocator.create<CodeGen>();
     g->pass1_arena = heap::ArenaAllocator::construct(&heap::c_allocator, &heap::c_allocator, "pass1");
src/codegen.hpp
@@ -11,17 +11,16 @@
 #include "parser.hpp"
 #include "errmsg.hpp"
 #include "target.hpp"
-#include "libc_installation.hpp"
 #include "userland.h"
 
 #include <stdio.h>
 
 CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget *target,
     OutType out_type, BuildMode build_mode, Buf *zig_lib_dir,
-    ZigLibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node);
+    Stage2LibCInstallation *libc, Buf *cache_dir, bool is_test_build, Stage2ProgressNode *progress_node);
 
 CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,
-        ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *progress_node);
+        Stage2LibCInstallation *libc, const char *name, Stage2ProgressNode *progress_node);
 
 void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len);
 void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len);
src/error.cpp
@@ -65,6 +65,20 @@ const char *err_str(Error err) {
         case ErrorInvalidLlvmCpuFeaturesFormat: return "invalid LLVM CPU features format";
         case ErrorUnknownApplicationBinaryInterface: return "unknown application binary interface";
         case ErrorASTUnitFailure: return "compiler bug: clang encountered a compile error, but the libclang API does not expose the error. See https://github.com/ziglang/zig/issues/4455 for more details";
+        case ErrorBadPathName: return "bad path name";
+        case ErrorSymLinkLoop: return "sym link loop";
+        case ErrorProcessFdQuotaExceeded: return "process fd quota exceeded";
+        case ErrorSystemFdQuotaExceeded: return "system fd quota exceeded";
+        case ErrorNoDevice: return "no device";
+        case ErrorDeviceBusy: return "device busy";
+        case ErrorUnableToSpawnCCompiler: return "unable to spawn system C compiler";
+        case ErrorCCompilerExitCode: return "system C compiler exited with failure code";
+        case ErrorCCompilerCrashed: return "system C compiler crashed";
+        case ErrorCCompilerCannotFindHeaders: return "system C compiler cannot find libc headers";
+        case ErrorLibCRuntimeNotFound: return "libc runtime not found";
+        case ErrorLibCStdLibHeaderNotFound: return "libc std lib headers not found";
+        case ErrorLibCKernel32LibNotFound: return "kernel32 library not found";
+        case ErrorUnsupportedArchitecture: return "unsupported architecture";
     }
     return "(invalid error)";
 }
src/libc_installation.cpp
@@ -1,498 +0,0 @@
-/*
- * Copyright (c) 2019 Andrew Kelley
- *
- * This file is part of zig, which is MIT licensed.
- * See http://opensource.org/licenses/MIT
- */
-
-#include "libc_installation.hpp"
-#include "os.hpp"
-#include "windows_sdk.h"
-#include "target.hpp"
-
-static const char *zig_libc_keys[] = {
-    "include_dir",
-    "sys_include_dir",
-    "crt_dir",
-    "static_crt_dir",
-    "msvc_lib_dir",
-    "kernel32_lib_dir",
-};
-
-static const size_t zig_libc_keys_len = array_length(zig_libc_keys);
-
-static bool zig_libc_match_key(Slice<uint8_t> name, Slice<uint8_t> value, bool *found_keys,
-        size_t index, Buf *field_ptr)
-{
-    if (!memEql(name, str(zig_libc_keys[index]))) return false;
-    buf_init_from_mem(field_ptr, (const char*)value.ptr, value.len);
-    found_keys[index] = true;
-    return true;
-}
-
-static void zig_libc_init_empty(ZigLibCInstallation *libc) {
-    *libc = {};
-    buf_init_from_str(&libc->include_dir, "");
-    buf_init_from_str(&libc->sys_include_dir, "");
-    buf_init_from_str(&libc->crt_dir, "");
-    buf_init_from_str(&libc->static_crt_dir, "");
-    buf_init_from_str(&libc->msvc_lib_dir, "");
-    buf_init_from_str(&libc->kernel32_lib_dir, "");
-}
-
-Error zig_libc_parse(ZigLibCInstallation *libc, Buf *libc_file, const ZigTarget *target, bool verbose) {
-    Error err;
-    zig_libc_init_empty(libc);
-
-    bool found_keys[array_length(zig_libc_keys)] = {};
-
-    Buf *contents = buf_alloc();
-    if ((err = os_fetch_file_path(libc_file, contents))) {
-        if (err != ErrorFileNotFound && verbose) {
-            fprintf(stderr, "Unable to read '%s': %s\n", buf_ptr(libc_file), err_str(err));
-        }
-        return err;
-    }
-
-    SplitIterator it = memSplit(buf_to_slice(contents), str("\n"));
-    for (;;) {
-        Optional<Slice<uint8_t>> opt_line = SplitIterator_next(&it);
-        if (!opt_line.is_some)
-            break;
-
-        if (opt_line.value.len == 0 || opt_line.value.ptr[0] == '#')
-            continue;
-
-        SplitIterator line_it = memSplit(opt_line.value, str("="));
-        Slice<uint8_t> name;
-        if (!SplitIterator_next(&line_it).unwrap(&name)) {
-            if (verbose) {
-                fprintf(stderr, "missing equal sign after field name\n");
-            }
-            return ErrorSemanticAnalyzeFail;
-        }
-        Slice<uint8_t> value = SplitIterator_rest(&line_it);
-        bool match = false;
-        match = match || zig_libc_match_key(name, value, found_keys, 0, &libc->include_dir);
-        match = match || zig_libc_match_key(name, value, found_keys, 1, &libc->sys_include_dir);
-        match = match || zig_libc_match_key(name, value, found_keys, 2, &libc->crt_dir);
-        match = match || zig_libc_match_key(name, value, found_keys, 3, &libc->static_crt_dir);
-        match = match || zig_libc_match_key(name, value, found_keys, 4, &libc->msvc_lib_dir);
-        match = match || zig_libc_match_key(name, value, found_keys, 5, &libc->kernel32_lib_dir);
-    }
-
-    for (size_t i = 0; i < zig_libc_keys_len; i += 1) {
-        if (!found_keys[i]) {
-            if (verbose) {
-                fprintf(stderr, "missing field: %s\n", zig_libc_keys[i]);
-            }
-            return ErrorSemanticAnalyzeFail;
-        }
-    }
-
-    if (buf_len(&libc->include_dir) == 0) {
-        if (verbose) {
-            fprintf(stderr, "include_dir may not be empty\n");
-        }
-        return ErrorSemanticAnalyzeFail;
-    }
-
-    if (buf_len(&libc->sys_include_dir) == 0) {
-        if (verbose) {
-            fprintf(stderr, "sys_include_dir may not be empty\n");
-        }
-        return ErrorSemanticAnalyzeFail;
-    }
-
-    if (buf_len(&libc->crt_dir) == 0) {
-        if (!target_os_is_darwin(target->os)) {
-            if (verbose) {
-                fprintf(stderr, "crt_dir may not be empty for %s\n", target_os_name(target->os));
-            }
-            return ErrorSemanticAnalyzeFail;
-        }
-    }
-
-    if (buf_len(&libc->static_crt_dir) == 0) {
-        if (target->os == OsWindows && target_abi_is_gnu(target->abi)) {
-            if (verbose) {
-                fprintf(stderr, "static_crt_dir may not be empty for %s\n", target_os_name(target->os));
-            }
-            return ErrorSemanticAnalyzeFail;
-        }
-    }
-
-    if (buf_len(&libc->msvc_lib_dir) == 0) {
-        if (target->os == OsWindows && !target_abi_is_gnu(target->abi)) {
-            if (verbose) {
-                fprintf(stderr, "msvc_lib_dir may not be empty for %s\n", target_os_name(target->os));
-            }
-            return ErrorSemanticAnalyzeFail;
-        }
-    }
-
-    if (buf_len(&libc->kernel32_lib_dir) == 0) {
-        if (target->os == OsWindows && !target_abi_is_gnu(target->abi)) {
-            if (verbose) {
-                fprintf(stderr, "kernel32_lib_dir may not be empty for %s\n", target_os_name(target->os));
-            }
-            return ErrorSemanticAnalyzeFail;
-        }
-    }
-
-    return ErrorNone;
-}
-
-#if defined(ZIG_OS_WINDOWS)
-#define CC_EXE "cc.exe"
-#else
-#define CC_EXE "cc"
-#endif
-
-static Error zig_libc_find_native_include_dir_posix(ZigLibCInstallation *self, bool verbose) {
-    const char *cc_exe = getenv("CC");
-    cc_exe = (cc_exe == nullptr) ? CC_EXE : cc_exe;
-    ZigList<const char *> args = {};
-    args.append(cc_exe);
-    args.append("-E");
-    args.append("-Wp,-v");
-    args.append("-xc");
-    #if defined(ZIG_OS_WINDOWS)
-    args.append("nul");
-    #else
-    args.append("/dev/null");
-    #endif
-
-    Termination term;
-    Buf *out_stderr = buf_alloc();
-    Buf *out_stdout = buf_alloc();
-    Error err;
-    if ((err = os_exec_process(args, &term, out_stderr, out_stdout))) {
-        if (verbose) {
-            fprintf(stderr, "unable to determine libc include path: executing '%s': %s\n", cc_exe, err_str(err));
-        }
-        return err;
-    }
-    if (term.how != TerminationIdClean || term.code != 0) {
-        if (verbose) {
-            fprintf(stderr, "unable to determine libc include path: executing '%s' failed\n", cc_exe);
-        }
-        return ErrorCCompileErrors;
-    }
-    char *prev_newline = buf_ptr(out_stderr);
-    ZigList<const char *> search_paths = {};
-    for (;;) {
-        char *newline = strchr(prev_newline, '\n');
-        if (newline == nullptr) {
-            break;
-        }
-
-        #if defined(ZIG_OS_WINDOWS)
-        *(newline - 1) = 0;
-        #endif
-        *newline = 0;
-
-        if (prev_newline[0] == ' ') {
-            search_paths.append(prev_newline);
-        }
-        prev_newline = newline + 1;
-    }
-    if (search_paths.length == 0) {
-        if (verbose) {
-            fprintf(stderr, "unable to determine libc include path: '%s' cannot find libc headers\n", cc_exe);
-        }
-        return ErrorCCompileErrors;
-    }
-    for (size_t i = 0; i < search_paths.length; i += 1) {
-        // search in reverse order
-        const char *search_path = search_paths.items[search_paths.length - i - 1];
-        // cut off spaces
-        while (*search_path == ' ') {
-            search_path += 1;
-        }
-
-        #if defined(ZIG_OS_WINDOWS)
-        if (buf_len(&self->include_dir) == 0) {
-            Buf *stdlib_path = buf_sprintf("%s\\stdlib.h", search_path);
-            bool exists;
-            if ((err = os_file_exists(stdlib_path, &exists))) {
-                exists = false;
-            }
-            if (exists) {
-                buf_init_from_str(&self->include_dir, search_path);
-            }
-        }
-        if (buf_len(&self->sys_include_dir) == 0) {
-            Buf *stdlib_path = buf_sprintf("%s\\sys\\types.h", search_path);
-            bool exists;
-            if ((err = os_file_exists(stdlib_path, &exists))) {
-                exists = false;
-            }
-            if (exists) {
-                buf_init_from_str(&self->sys_include_dir, search_path);
-            }
-        }
-        #else
-        if (buf_len(&self->include_dir) == 0) {
-            Buf *stdlib_path = buf_sprintf("%s/stdlib.h", search_path);
-            bool exists;
-            if ((err = os_file_exists(stdlib_path, &exists))) {
-                exists = false;
-            }
-            if (exists) {
-                buf_init_from_str(&self->include_dir, search_path);
-            }
-        }
-        if (buf_len(&self->sys_include_dir) == 0) {
-            Buf *stdlib_path = buf_sprintf("%s/sys/errno.h", search_path);
-            bool exists;
-            if ((err = os_file_exists(stdlib_path, &exists))) {
-                exists = false;
-            }
-            if (exists) {
-                buf_init_from_str(&self->sys_include_dir, search_path);
-            }
-        }
-        #endif
-
-        if (buf_len(&self->include_dir) != 0 && buf_len(&self->sys_include_dir) != 0) {
-            return ErrorNone;
-        }
-    }
-    if (verbose) {
-        if (buf_len(&self->include_dir) == 0) {
-            fprintf(stderr, "unable to determine libc include path: stdlib.h not found in '%s' search paths\n", cc_exe);
-        }
-        if (buf_len(&self->sys_include_dir) == 0) {
-            #if defined(ZIG_OS_WINDOWS)
-            fprintf(stderr, "unable to determine libc include path: sys/types.h not found in '%s' search paths\n", cc_exe);
-            #else
-            fprintf(stderr, "unable to determine libc include path: sys/errno.h not found in '%s' search paths\n", cc_exe);
-            #endif
-        }
-    }
-    return ErrorFileNotFound;
-}
-
-Error zig_libc_cc_print_file_name(const char *o_file, Buf *out, bool want_dirname, bool verbose) {
-    const char *cc_exe = getenv("CC");
-    cc_exe = (cc_exe == nullptr) ? CC_EXE : cc_exe;
-    ZigList<const char *> args = {};
-    args.append(cc_exe);
-    args.append(buf_ptr(buf_sprintf("-print-file-name=%s", o_file)));
-    Termination term;
-    Buf *out_stderr = buf_alloc();
-    Buf *out_stdout = buf_alloc();
-    Error err;
-    if ((err = os_exec_process(args, &term, out_stderr, out_stdout))) {
-        if (err == ErrorFileNotFound)
-            return ErrorNoCCompilerInstalled;
-        if (verbose) {
-            fprintf(stderr, "unable to determine libc library path: executing '%s': %s\n", cc_exe, err_str(err));
-        }
-        return err;
-    }
-    if (term.how != TerminationIdClean || term.code != 0) {
-        if (verbose) {
-            fprintf(stderr, "unable to determine libc library path: executing '%s' failed\n", cc_exe);
-        }
-        return ErrorCCompileErrors;
-    }
-    #if defined(ZIG_OS_WINDOWS)
-    if (buf_ends_with_str(out_stdout, "\r\n")) {
-        buf_resize(out_stdout, buf_len(out_stdout) - 2);
-    }
-    #else
-    if (buf_ends_with_str(out_stdout, "\n")) {
-        buf_resize(out_stdout, buf_len(out_stdout) - 1);
-    }
-    #endif
-    if (buf_len(out_stdout) == 0 || buf_eql_str(out_stdout, o_file)) {
-        return ErrorCCompilerCannotFindFile;
-    }
-    if (want_dirname) {
-        os_path_dirname(out_stdout, out);
-    } else {
-        buf_init_from_buf(out, out_stdout);
-    }
-    return ErrorNone;
-}
-
-#undef CC_EXE
-
-#if defined(ZIG_OS_WINDOWS) || defined(ZIG_OS_LINUX) || defined(ZIG_OS_DRAGONFLY)
-static Error zig_libc_find_native_crt_dir_posix(ZigLibCInstallation *self, bool verbose) {
-    return zig_libc_cc_print_file_name("crt1.o", &self->crt_dir, true, verbose);
-}
-#endif
-
-#if defined(ZIG_OS_WINDOWS)
-static Error zig_libc_find_native_static_crt_dir_posix(ZigLibCInstallation *self, bool verbose) {
-    return zig_libc_cc_print_file_name("crtbegin.o", &self->static_crt_dir, true, verbose);
-}
-
-static Error zig_libc_find_native_include_dir_windows(ZigLibCInstallation *self, ZigWindowsSDK *sdk, bool verbose) {
-    Error err;
-    if ((err = os_get_win32_ucrt_include_path(sdk, &self->include_dir))) {
-        if (verbose) {
-            fprintf(stderr, "Unable to determine libc include path: %s\n", err_str(err));
-        }
-        return err;
-    }
-    return ErrorNone;
-}
-
-static Error zig_libc_find_native_crt_dir_windows(ZigLibCInstallation *self, ZigWindowsSDK *sdk, ZigTarget *target,
-        bool verbose)
-{
-    Error err;
-    if ((err = os_get_win32_ucrt_lib_path(sdk, &self->crt_dir, target->arch))) {
-        if (verbose) {
-            fprintf(stderr, "Unable to determine ucrt path: %s\n", err_str(err));
-        }
-        return err;
-    }
-    return ErrorNone;
-}
-
-static Error zig_libc_find_kernel32_lib_dir(ZigLibCInstallation *self, ZigWindowsSDK *sdk, ZigTarget *target,
-        bool verbose)
-{
-    Error err;
-    if ((err = os_get_win32_kern32_path(sdk, &self->kernel32_lib_dir, target->arch))) {
-        if (verbose) {
-            fprintf(stderr, "Unable to determine kernel32 path: %s\n", err_str(err));
-        }
-        return err;
-    }
-    return ErrorNone;
-}
-
-static Error zig_libc_find_native_msvc_lib_dir(ZigLibCInstallation *self, ZigWindowsSDK *sdk, bool verbose) {
-    if (sdk->msvc_lib_dir_ptr == nullptr) {
-        if (verbose) {
-            fprintf(stderr, "Unable to determine vcruntime.lib path\n");
-        }
-        return ErrorFileNotFound;
-    }
-    buf_init_from_mem(&self->msvc_lib_dir, sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len);
-    return ErrorNone;
-}
-
-static Error zig_libc_find_native_msvc_include_dir(ZigLibCInstallation *self, ZigWindowsSDK *sdk, bool verbose) {
-    Error err;
-    if (sdk->msvc_lib_dir_ptr == nullptr) {
-        if (verbose) {
-            fprintf(stderr, "Unable to determine vcruntime.h path\n");
-        }
-        return ErrorFileNotFound;
-    }
-    Buf search_path = BUF_INIT;
-    buf_init_from_mem(&search_path, sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len);
-    buf_append_str(&search_path, "..\\..\\include");
-
-    Buf *vcruntime_path = buf_sprintf("%s\\vcruntime.h", buf_ptr(&search_path));
-    bool exists;
-    if ((err = os_file_exists(vcruntime_path, &exists))) {
-        exists = false;
-    }
-    if (exists) {
-        self->sys_include_dir = search_path;
-        return ErrorNone;
-    }
-
-    if (verbose) {
-        fprintf(stderr, "Unable to determine vcruntime.h path\n");
-    }
-    return ErrorFileNotFound;
-}
-#endif
-
-void zig_libc_render(ZigLibCInstallation *self, FILE *file) {
-    fprintf(file,
-        "# The directory that contains `stdlib.h`.\n"
-        "# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`\n"
-        "include_dir=%s\n"
-        "\n"
-        "# The system-specific include directory. May be the same as `include_dir`.\n"
-        "# On Windows it's the directory that includes `vcruntime.h`.\n"
-        "# On POSIX it's the directory that includes `sys/errno.h`.\n"
-        "sys_include_dir=%s\n"
-        "\n"
-        "# The directory that contains `crt1.o` or `crt2.o`.\n"
-        "# On POSIX, can be found with `cc -print-file-name=crt1.o`.\n"
-        "# Not needed when targeting MacOS.\n"
-        "crt_dir=%s\n"
-        "\n"
-        "# The directory that contains `crtbegin.o`.\n"
-        "# On POSIX, can be found with `cc -print-file-name=crtbegin.o`.\n"
-        "# Not needed when targeting MacOS.\n"
-        "static_crt_dir=%s\n"
-        "\n"
-        "# The directory that contains `vcruntime.lib`.\n"
-        "# Only needed when targeting MSVC on Windows.\n"
-        "msvc_lib_dir=%s\n"
-        "\n"
-        "# The directory that contains `kernel32.lib`.\n"
-        "# Only needed when targeting MSVC on Windows.\n"
-        "kernel32_lib_dir=%s\n"
-        "\n",
-        buf_ptr(&self->include_dir),
-        buf_ptr(&self->sys_include_dir),
-        buf_ptr(&self->crt_dir),
-        buf_ptr(&self->static_crt_dir),
-        buf_ptr(&self->msvc_lib_dir),
-        buf_ptr(&self->kernel32_lib_dir)
-    );
-}
-
-Error zig_libc_find_native(ZigLibCInstallation *self, bool verbose) {
-    Error err;
-    zig_libc_init_empty(self);
-#if defined(ZIG_OS_WINDOWS)
-    ZigTarget native_target;
-    get_native_target(&native_target);
-    if (target_abi_is_gnu(native_target.abi)) {
-        if ((err = zig_libc_find_native_include_dir_posix(self, verbose)))
-            return err;
-        if ((err = zig_libc_find_native_crt_dir_posix(self, verbose)))
-            return err;
-        if ((err = zig_libc_find_native_static_crt_dir_posix(self, verbose)))
-            return err;
-        return ErrorNone;
-    } else {
-        ZigWindowsSDK *sdk;
-        switch (zig_find_windows_sdk(&sdk)) {
-            case ZigFindWindowsSdkErrorNone:
-                if ((err = zig_libc_find_native_msvc_include_dir(self, sdk, verbose)))
-                    return err;
-                if ((err = zig_libc_find_native_msvc_lib_dir(self, sdk, verbose)))
-                    return err;
-                if ((err = zig_libc_find_kernel32_lib_dir(self, sdk, &native_target, verbose)))
-                    return err;
-                if ((err = zig_libc_find_native_include_dir_windows(self, sdk, verbose)))
-                    return err;
-                if ((err = zig_libc_find_native_crt_dir_windows(self, sdk, &native_target, verbose)))
-                    return err;
-                return ErrorNone;
-            case ZigFindWindowsSdkErrorOutOfMemory:
-                return ErrorNoMem;
-            case ZigFindWindowsSdkErrorNotFound:
-                return ErrorFileNotFound;
-            case ZigFindWindowsSdkErrorPathTooLong:
-                return ErrorPathTooLong;
-        }
-    }
-    zig_unreachable();
-#else
-    if ((err = zig_libc_find_native_include_dir_posix(self, verbose)))
-        return err;
-#if defined(ZIG_OS_FREEBSD) || defined(ZIG_OS_NETBSD)
-    buf_init_from_str(&self->crt_dir, "/usr/lib");
-#elif defined(ZIG_OS_LINUX) || defined(ZIG_OS_DRAGONFLY)
-    if ((err = zig_libc_find_native_crt_dir_posix(self, verbose)))
-        return err;
-#endif
-    return ErrorNone;
-#endif
-}
src/libc_installation.hpp
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2019 Andrew Kelley
- *
- * This file is part of zig, which is MIT licensed.
- * See http://opensource.org/licenses/MIT
- */
-
-#ifndef ZIG_LIBC_INSTALLATION_HPP
-#define ZIG_LIBC_INSTALLATION_HPP
-
-#include <stdio.h>
-
-#include "buffer.hpp"
-#include "error.hpp"
-#include "target.hpp"
-
-// Must be synchronized with zig_libc_keys
-struct ZigLibCInstallation {
-    Buf include_dir;
-    Buf sys_include_dir;
-    Buf crt_dir;
-    Buf static_crt_dir;
-    Buf msvc_lib_dir;
-    Buf kernel32_lib_dir;
-};
-
-Error ATTRIBUTE_MUST_USE zig_libc_parse(ZigLibCInstallation *libc, Buf *libc_file,
-        const ZigTarget *target, bool verbose);
-void zig_libc_render(ZigLibCInstallation *self, FILE *file);
-
-Error ATTRIBUTE_MUST_USE zig_libc_find_native(ZigLibCInstallation *self, bool verbose);
-
-Error zig_libc_cc_print_file_name(const char *o_file, Buf *out, bool want_dirname, bool verbose);
-
-#endif
src/link.cpp
@@ -1483,7 +1483,7 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file, Stage2Pr
     } else {
         assert(parent->libc != nullptr);
         Buf *out_buf = buf_alloc();
-        os_path_join(&parent->libc->crt_dir, buf_create_from_str(file), out_buf);
+        os_path_join(buf_create_from_str(parent->libc->crt_dir), buf_create_from_str(file), out_buf);
         return buf_ptr(out_buf);
     }
 }
@@ -1747,7 +1747,7 @@ static void construct_linker_job_elf(LinkJob *lj) {
     if (g->libc_link_lib != nullptr) {
         if (g->libc != nullptr) {
             lj->args.append("-L");
-            lj->args.append(buf_ptr(&g->libc->crt_dir));
+            lj->args.append(g->libc->crt_dir);
         }
 
         if (g->have_dynamic_link && (is_dyn_lib || g->out_type == OutTypeExe)) {
@@ -2251,14 +2251,14 @@ static void construct_linker_job_coff(LinkJob *lj) {
     lj->args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(&g->output_file_path))));
 
     if (g->libc_link_lib != nullptr && g->libc != nullptr) {
-        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->crt_dir))));
+        lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", g->libc->crt_dir)));
 
         if (target_abi_is_gnu(g->zig_target->abi)) {
-            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->sys_include_dir))));
-            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->include_dir))));
+            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", g->libc->sys_include_dir)));
+            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", g->libc->include_dir)));
         } else {
-            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->msvc_lib_dir))));
-            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", buf_ptr(&g->libc->kernel32_lib_dir))));
+            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", g->libc->msvc_lib_dir)));
+            lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", g->libc->kernel32_lib_dir)));
         }
     }
 
src/main.cpp
@@ -14,7 +14,6 @@
 #include "heap.hpp"
 #include "os.hpp"
 #include "target.hpp"
-#include "libc_installation.hpp"
 #include "userland.h"
 #include "glibc.hpp"
 #include "dump_analysis.hpp"
@@ -1027,15 +1026,22 @@ static int main0(int argc, char **argv) {
     switch (cmd) {
     case CmdLibC: {
         if (in_file) {
-            ZigLibCInstallation libc;
-            if ((err = zig_libc_parse(&libc, buf_create_from_str(in_file), &target, true)))
+            Stage2LibCInstallation libc;
+            if ((err = stage2_libc_parse(&libc, in_file))) {
+                fprintf(stderr, "unable to parse libc file: %s\n", err_str(err));
                 return main_exit(root_progress_node, EXIT_FAILURE);
+            }
             return main_exit(root_progress_node, EXIT_SUCCESS);
         }
-        ZigLibCInstallation libc;
-        if ((err = zig_libc_find_native(&libc, true)))
+        Stage2LibCInstallation libc;
+        if ((err = stage2_libc_find_native(&libc))) {
+            fprintf(stderr, "unable to find native libc file: %s\n", err_str(err));
+            return main_exit(root_progress_node, EXIT_FAILURE);
+        }
+        if ((err = stage2_libc_render(&libc, stdout))) {
+            fprintf(stderr, "unable to print libc file: %s\n", err_str(err));
             return main_exit(root_progress_node, EXIT_FAILURE);
-        zig_libc_render(&libc, stdout);
+        }
         return main_exit(root_progress_node, EXIT_SUCCESS);
     }
     case CmdBuiltin: {
@@ -1125,10 +1131,10 @@ static int main0(int argc, char **argv) {
             if (cmd == CmdRun && buf_out_name == nullptr) {
                 buf_out_name = buf_create_from_str("run");
             }
-            ZigLibCInstallation *libc = nullptr;
+            Stage2LibCInstallation *libc = nullptr;
             if (libc_txt != nullptr) {
-                libc = heap::c_allocator.create<ZigLibCInstallation>();
-                if ((err = zig_libc_parse(libc, buf_create_from_str(libc_txt), &target, true))) {
+                libc = heap::c_allocator.create<Stage2LibCInstallation>();
+                if ((err = stage2_libc_parse(libc, libc_txt))) {
                     fprintf(stderr, "Unable to parse --libc text file: %s\n", err_str(err));
                     return main_exit(root_progress_node, EXIT_FAILURE);
                 }
src/os.cpp
@@ -1551,108 +1551,6 @@ void os_stderr_set_color(TermColor color) {
 #endif
 }
 
-Error os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
-#if defined(ZIG_OS_WINDOWS)
-    buf_resize(output_buf, 0);
-    buf_appendf(output_buf, "%sLib\\%s\\ucrt\\", sdk->path10_ptr, sdk->version10_ptr);
-    switch (platform_type) {
-    case ZigLLVM_x86:
-        buf_append_str(output_buf, "x86\\");
-        break;
-    case ZigLLVM_x86_64:
-        buf_append_str(output_buf, "x64\\");
-        break;
-    case ZigLLVM_arm:
-        buf_append_str(output_buf, "arm\\");
-        break;
-    default:
-        zig_panic("Attempted to use vcruntime for non-supported platform.");
-    }
-    Buf* tmp_buf = buf_alloc();
-    buf_init_from_buf(tmp_buf, output_buf);
-    buf_append_str(tmp_buf, "ucrt.lib");
-    if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
-        return ErrorNone;
-    }
-    else {
-        buf_resize(output_buf, 0);
-        return ErrorFileNotFound;
-    }
-#else
-    return ErrorFileNotFound;
-#endif
-}
-
-Error os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) {
-#if defined(ZIG_OS_WINDOWS)
-    buf_resize(output_buf, 0);
-    buf_appendf(output_buf, "%sInclude\\%s\\ucrt", sdk->path10_ptr, sdk->version10_ptr);
-    if (GetFileAttributesA(buf_ptr(output_buf)) != INVALID_FILE_ATTRIBUTES) {
-        return ErrorNone;
-    }
-    else {
-        buf_resize(output_buf, 0);
-        return ErrorFileNotFound;
-    }
-#else
-    return ErrorFileNotFound;
-#endif
-}
-
-Error os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
-#if defined(ZIG_OS_WINDOWS)
-    {
-        buf_resize(output_buf, 0);
-        buf_appendf(output_buf, "%sLib\\%s\\um\\", sdk->path10_ptr, sdk->version10_ptr);
-        switch (platform_type) {
-        case ZigLLVM_x86:
-            buf_append_str(output_buf, "x86\\");
-            break;
-        case ZigLLVM_x86_64:
-            buf_append_str(output_buf, "x64\\");
-            break;
-        case ZigLLVM_arm:
-            buf_append_str(output_buf, "arm\\");
-            break;
-        default:
-            zig_panic("Attempted to use vcruntime for non-supported platform.");
-        }
-        Buf* tmp_buf = buf_alloc();
-        buf_init_from_buf(tmp_buf, output_buf);
-        buf_append_str(tmp_buf, "kernel32.lib");
-        if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
-            return ErrorNone;
-        }
-    }
-    {
-        buf_resize(output_buf, 0);
-        buf_appendf(output_buf, "%sLib\\%s\\um\\", sdk->path81_ptr, sdk->version81_ptr);
-        switch (platform_type) {
-        case ZigLLVM_x86:
-            buf_append_str(output_buf, "x86\\");
-            break;
-        case ZigLLVM_x86_64:
-            buf_append_str(output_buf, "x64\\");
-            break;
-        case ZigLLVM_arm:
-            buf_append_str(output_buf, "arm\\");
-            break;
-        default:
-            zig_panic("Attempted to use vcruntime for non-supported platform.");
-        }
-        Buf* tmp_buf = buf_alloc();
-        buf_init_from_buf(tmp_buf, output_buf);
-        buf_append_str(tmp_buf, "kernel32.lib");
-        if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
-            return ErrorNone;
-        }
-    }
-    return ErrorFileNotFound;
-#else
-    return ErrorFileNotFound;
-#endif
-}
-
 #if defined(ZIG_OS_WINDOWS)
 // Ported from std/unicode.zig
 struct Utf16LeIterator {
src/os.hpp
@@ -152,10 +152,6 @@ Error ATTRIBUTE_MUST_USE os_self_exe_path(Buf *out_path);
 
 Error ATTRIBUTE_MUST_USE os_get_app_data_dir(Buf *out_path, const char *appname);
 
-Error ATTRIBUTE_MUST_USE os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf);
-Error ATTRIBUTE_MUST_USE os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
-Error ATTRIBUTE_MUST_USE os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
-
 Error ATTRIBUTE_MUST_USE os_self_exe_shared_libs(ZigList<Buf *> &paths);
 
 #endif
src/userland.cpp
@@ -144,3 +144,25 @@ int stage2_cmd_targets(const char *zig_triple) {
     const char *msg = "stage0 called stage2_cmd_targets";
     stage2_panic(msg, strlen(msg));
 }
+
+enum Error stage2_libc_parse(struct Stage2LibCInstallation *libc, const char *libc_file) {
+    const char *msg = "stage0 called stage2_libc_parse";
+    stage2_panic(msg, strlen(msg));
+}
+
+enum Error stage2_libc_render(struct Stage2LibCInstallation *self, FILE *file) {
+    const char *msg = "stage0 called stage2_libc_render";
+    stage2_panic(msg, strlen(msg));
+}
+
+enum Error stage2_libc_find_native(struct Stage2LibCInstallation *libc) {
+    const char *msg = "stage0 called stage2_libc_find_native";
+    stage2_panic(msg, strlen(msg));
+}
+
+enum Error stage2_libc_cc_print_file_name(char **out_ptr, size_t *out_len,
+        const char *o_file, bool want_dirname)
+{
+    const char *msg = "stage0 called stage2_libc_cc_print_file_name";
+    stage2_panic(msg, strlen(msg));
+}
src/userland.h
@@ -85,6 +85,20 @@ enum Error {
     ErrorInvalidLlvmCpuFeaturesFormat,
     ErrorUnknownApplicationBinaryInterface,
     ErrorASTUnitFailure,
+    ErrorBadPathName,
+    ErrorSymLinkLoop,
+    ErrorProcessFdQuotaExceeded,
+    ErrorSystemFdQuotaExceeded,
+    ErrorNoDevice,
+    ErrorDeviceBusy,
+    ErrorUnableToSpawnCCompiler,
+    ErrorCCompilerExitCode,
+    ErrorCCompilerCrashed,
+    ErrorCCompilerCannotFindHeaders,
+    ErrorLibCRuntimeNotFound,
+    ErrorLibCStdLibHeaderNotFound,
+    ErrorLibCKernel32LibNotFound,
+    ErrorUnsupportedArchitecture,
 };
 
 // ABI warning
@@ -185,7 +199,7 @@ ZIG_EXTERN_C void stage2_progress_update_node(Stage2ProgressNode *node,
 struct Stage2CpuFeatures;
 
 // ABI warning
-ZIG_EXTERN_C Error stage2_cpu_features_parse(struct Stage2CpuFeatures **result,
+ZIG_EXTERN_C enum Error stage2_cpu_features_parse(struct Stage2CpuFeatures **result,
         const char *zig_triple, const char *cpu_name, const char *cpu_features);
 
 // ABI warning
@@ -205,5 +219,30 @@ ZIG_EXTERN_C void stage2_cpu_features_get_cache_hash(const struct Stage2CpuFeatu
 // ABI warning
 ZIG_EXTERN_C int stage2_cmd_targets(const char *zig_triple);
 
+// ABI warning
+struct Stage2LibCInstallation {
+    const char *include_dir;
+    size_t include_dir_len;
+    const char *sys_include_dir;
+    size_t sys_include_dir_len;
+    const char *crt_dir;
+    size_t crt_dir_len;
+    const char *static_crt_dir;
+    size_t static_crt_dir_len;
+    const char *msvc_lib_dir;
+    size_t msvc_lib_dir_len;
+    const char *kernel32_lib_dir;
+    size_t kernel32_lib_dir_len;
+};
+
+// ABI warning
+ZIG_EXTERN_C enum Error stage2_libc_parse(struct Stage2LibCInstallation *libc, const char *libc_file);
+// ABI warning
+ZIG_EXTERN_C enum Error stage2_libc_render(struct Stage2LibCInstallation *self, FILE *file);
+// ABI warning
+ZIG_EXTERN_C enum Error stage2_libc_find_native(struct Stage2LibCInstallation *libc);
+// ABI warning
+ZIG_EXTERN_C enum Error stage2_libc_cc_print_file_name(char **out_ptr, size_t *out_len,
+        const char *o_file, bool want_dirname);
 
 #endif
src/windows_sdk.h
@@ -16,6 +16,7 @@
 
 #include <stddef.h>
 
+// ABI warning - src-self-hosted/windows_sdk.zig
 struct ZigWindowsSDK {
     const char *path10_ptr;
     size_t path10_len;
@@ -33,6 +34,7 @@ struct ZigWindowsSDK {
     size_t msvc_lib_dir_len;
 };
 
+// ABI warning - src-self-hosted/windows_sdk.zig
 enum ZigFindWindowsSdkError {
     ZigFindWindowsSdkErrorNone,
     ZigFindWindowsSdkErrorOutOfMemory,
@@ -40,8 +42,10 @@ enum ZigFindWindowsSdkError {
     ZigFindWindowsSdkErrorPathTooLong,
 };
 
+// ABI warning - src-self-hosted/windows_sdk.zig
 ZIG_EXTERN_C enum ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk);
 
+// ABI warning - src-self-hosted/windows_sdk.zig
 ZIG_EXTERN_C void zig_free_windows_sdk(struct ZigWindowsSDK *sdk);
 
 #endif
src-self-hosted/c.zig
@@ -4,5 +4,4 @@ pub usingnamespace @cImport({
     @cInclude("inttypes.h");
     @cInclude("config.h");
     @cInclude("zig_llvm.h");
-    @cInclude("windows_sdk.h");
 });
src-self-hosted/libc_installation.zig
@@ -1,20 +1,29 @@
 const std = @import("std");
 const builtin = @import("builtin");
-const event = std.event;
 const util = @import("util.zig");
 const Target = std.Target;
-const c = @import("c.zig");
 const fs = std.fs;
 const Allocator = std.mem.Allocator;
+const Batch = std.event.Batch;
+
+const is_darwin = Target.current.isDarwin();
+const is_windows = Target.current.isWindows();
+const is_freebsd = Target.current.isFreeBSD();
+const is_netbsd = Target.current.isNetBSD();
+const is_linux = Target.current.isLinux();
+const is_dragonfly = Target.current.isDragonFlyBSD();
+const is_gnu = Target.current.isGnu();
+
+usingnamespace @import("windows_sdk.zig");
 
 /// See the render function implementation for documentation of the fields.
 pub const LibCInstallation = struct {
-    include_dir: []const u8,
-    lib_dir: ?[]const u8,
-    static_lib_dir: ?[]const u8,
-    msvc_lib_dir: ?[]const u8,
-    kernel32_lib_dir: ?[]const u8,
-    dynamic_linker_path: ?[]const u8,
+    include_dir: ?[:0]const u8 = null,
+    sys_include_dir: ?[:0]const u8 = null,
+    crt_dir: ?[:0]const u8 = null,
+    static_crt_dir: ?[:0]const u8 = null,
+    msvc_lib_dir: ?[:0]const u8 = null,
+    kernel32_lib_dir: ?[:0]const u8 = null,
 
     pub const FindError = error{
         OutOfMemory,
@@ -30,28 +39,20 @@ pub const LibCInstallation = struct {
     };
 
     pub fn parse(
-        self: *LibCInstallation,
         allocator: *Allocator,
         libc_file: []const u8,
         stderr: *std.io.OutStream(fs.File.WriteError),
-    ) !void {
-        self.initEmpty();
-
-        const keys = [_][]const u8{
-            "include_dir",
-            "lib_dir",
-            "static_lib_dir",
-            "msvc_lib_dir",
-            "kernel32_lib_dir",
-            "dynamic_linker_path",
-        };
+    ) !LibCInstallation {
+        var self: LibCInstallation = .{};
+
+        const fields = std.meta.fields(LibCInstallation);
         const FoundKey = struct {
             found: bool,
-            allocated: ?[]u8,
+            allocated: ?[:0]u8,
         };
-        var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** keys.len;
+        var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** fields.len;
         errdefer {
-            self.initEmpty();
+            self = .{};
             for (found_keys) |found_key| {
                 if (found_key.allocated) |s| allocator.free(s);
             }
@@ -69,152 +70,188 @@ pub const LibCInstallation = struct {
                 return error.ParseError;
             };
             const value = line_it.rest();
-            inline for (keys) |key, i| {
-                if (std.mem.eql(u8, name, key)) {
+            inline for (fields) |field, i| {
+                if (std.mem.eql(u8, name, field.name)) {
                     found_keys[i].found = true;
-                    switch (@typeInfo(@TypeOf(@field(self, key)))) {
-                        .Optional => {
-                            if (value.len == 0) {
-                                @field(self, key) = null;
-                            } else {
-                                found_keys[i].allocated = try std.mem.dupe(allocator, u8, value);
-                                @field(self, key) = found_keys[i].allocated;
-                            }
-                        },
-                        else => {
-                            if (value.len == 0) {
-                                try stderr.print("field cannot be empty: {}\n", .{key});
-                                return error.ParseError;
-                            }
-                            const dupe = try std.mem.dupe(allocator, u8, value);
-                            found_keys[i].allocated = dupe;
-                            @field(self, key) = dupe;
-                        },
+                    if (value.len == 0) {
+                        @field(self, field.name) = null;
+                    } else {
+                        found_keys[i].allocated = try std.mem.dupeZ(allocator, u8, value);
+                        @field(self, field.name) = found_keys[i].allocated;
                     }
                     break;
                 }
             }
         }
-        for (found_keys) |found_key, i| {
-            if (!found_key.found) {
-                try stderr.print("missing field: {}\n", .{keys[i]});
+        inline for (fields) |field, i| {
+            if (!found_keys[i].found) {
+                try stderr.print("missing field: {}\n", .{field.name});
                 return error.ParseError;
             }
         }
+        if (self.include_dir == null) {
+            try stderr.print("include_dir may not be empty\n", .{});
+            return error.ParseError;
+        }
+        if (self.sys_include_dir == null) {
+            try stderr.print("sys_include_dir may not be empty\n", .{});
+            return error.ParseError;
+        }
+        if (self.crt_dir == null and is_darwin) {
+            try stderr.print("crt_dir may not be empty for {}\n", .{@tagName(Target.current.getOs())});
+            return error.ParseError;
+        }
+        if (self.static_crt_dir == null and is_windows and is_gnu) {
+            try stderr.print("static_crt_dir may not be empty for {}-{}\n", .{
+                @tagName(Target.current.getOs()),
+                @tagName(Target.current.getAbi()),
+            });
+            return error.ParseError;
+        }
+        if (self.msvc_lib_dir == null and is_windows and !is_gnu) {
+            try stderr.print("msvc_lib_dir may not be empty for {}-{}\n", .{
+                @tagName(Target.current.getOs()),
+                @tagName(Target.current.getAbi()),
+            });
+            return error.ParseError;
+        }
+        if (self.kernel32_lib_dir == null and is_windows and !is_gnu) {
+            try stderr.print("kernel32_lib_dir may not be empty for {}-{}\n", .{
+                @tagName(Target.current.getOs()),
+                @tagName(Target.current.getAbi()),
+            });
+            return error.ParseError;
+        }
+
+        return self;
     }
 
-    pub fn render(self: *const LibCInstallation, out: *std.io.OutStream(fs.File.WriteError)) !void {
+    pub fn render(self: LibCInstallation, out: *std.io.OutStream(fs.File.WriteError)) !void {
         @setEvalBranchQuota(4000);
-        const lib_dir = self.lib_dir orelse "";
-        const static_lib_dir = self.static_lib_dir orelse "";
+        const include_dir = self.include_dir orelse "";
+        const sys_include_dir = self.sys_include_dir orelse "";
+        const crt_dir = self.crt_dir orelse "";
+        const static_crt_dir = self.static_crt_dir orelse "";
         const msvc_lib_dir = self.msvc_lib_dir orelse "";
         const kernel32_lib_dir = self.kernel32_lib_dir orelse "";
-        const dynamic_linker_path = self.dynamic_linker_path orelse util.getDynamicLinkerPath(Target{ .Native = {} });
+
         try out.print(
             \\# The directory that contains `stdlib.h`.
-            \\# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null`
+            \\# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`
             \\include_dir={}
             \\
-            \\# The directory that contains `crt1.o`.
-            \\# On Linux, can be found with `cc -print-file-name=crt1.o`.
+            \\# The system-specific include directory. May be the same as `include_dir`.
+            \\# On Windows it's the directory that includes `vcruntime.h`.
+            \\# On POSIX it's the directory that includes `sys/errno.h`.
+            \\sys_include_dir={}
+            \\
+            \\# The directory that contains `crt1.o` or `crt2.o`.
+            \\# On POSIX, can be found with `cc -print-file-name=crt1.o`.
             \\# Not needed when targeting MacOS.
-            \\lib_dir={}
+            \\crt_dir={}
             \\
             \\# The directory that contains `crtbegin.o`.
-            \\# On Linux, can be found with `cc -print-file-name=crtbegin.o`.
-            \\# Not needed when targeting MacOS or Windows.
-            \\static_lib_dir={}
+            \\# On POSIX, can be found with `cc -print-file-name=crtbegin.o`.
+            \\# Not needed when targeting MacOS.
+            \\static_crt_dir={}
             \\
             \\# The directory that contains `vcruntime.lib`.
-            \\# Only needed when targeting Windows.
+            \\# Only needed when targeting MSVC on Windows.
             \\msvc_lib_dir={}
             \\
             \\# The directory that contains `kernel32.lib`.
-            \\# Only needed when targeting Windows.
+            \\# Only needed when targeting MSVC on Windows.
             \\kernel32_lib_dir={}
             \\
-            \\# The full path to the dynamic linker, on the target system.
-            \\# Only needed when targeting Linux.
-            \\dynamic_linker_path={}
-            \\
-        , .{ self.include_dir, lib_dir, static_lib_dir, msvc_lib_dir, kernel32_lib_dir, dynamic_linker_path });
+        , .{
+            include_dir,
+            sys_include_dir,
+            crt_dir,
+            static_crt_dir,
+            msvc_lib_dir,
+            kernel32_lib_dir,
+        });
     }
 
     /// Finds the default, native libc.
-    pub fn findNative(self: *LibCInstallation, allocator: *Allocator) !void {
-        self.initEmpty();
-        var group = event.Group(FindError!void).init(allocator);
-        errdefer group.wait() catch {};
-        var windows_sdk: ?*c.ZigWindowsSDK = null;
-        errdefer if (windows_sdk) |sdk| c.zig_free_windows_sdk(@ptrCast(?[*]c.ZigWindowsSDK, sdk));
-
-        switch (builtin.os) {
-            .windows => {
-                var sdk: *c.ZigWindowsSDK = undefined;
-                switch (c.zig_find_windows_sdk(@ptrCast(?[*]?[*]c.ZigWindowsSDK, &sdk))) {
-                    c.ZigFindWindowsSdkError.None => {
-                        windows_sdk = sdk;
-
-                        if (sdk.msvc_lib_dir_ptr != 0) {
-                            self.msvc_lib_dir = try std.mem.dupe(allocator, u8, sdk.msvc_lib_dir_ptr[0..sdk.msvc_lib_dir_len]);
-                        }
-                        try group.call(findNativeKernel32LibDir, .{ allocator, self, sdk });
-                        try group.call(findNativeIncludeDirWindows, .{ self, allocator, sdk });
-                        try group.call(findNativeLibDirWindows, .{ self, allocator, sdk });
+    pub fn findNative(allocator: *Allocator) !LibCInstallation {
+        var self: LibCInstallation = .{};
+
+        if (is_windows) {
+            if (is_gnu) {
+                var batch = Batch(FindError!void, 3, .auto_async).init();
+                batch.add(&async self.findNativeIncludeDirPosix(allocator));
+                batch.add(&async self.findNativeCrtDirPosix(allocator));
+                batch.add(&async self.findNativeStaticCrtDirPosix(allocator));
+                try batch.wait();
+            } else {
+                var sdk: *ZigWindowsSDK = undefined;
+                switch (zig_find_windows_sdk(&sdk)) {
+                    .None => {
+                        defer zig_free_windows_sdk(sdk);
+
+                        var batch = Batch(FindError!void, 5, .auto_async).init();
+                        batch.add(&async self.findNativeMsvcIncludeDir(allocator, sdk));
+                        batch.add(&async self.findNativeMsvcLibDir(allocator, sdk));
+                        batch.add(&async self.findNativeKernel32LibDir(allocator, sdk));
+                        batch.add(&async self.findNativeIncludeDirWindows(allocator, sdk));
+                        batch.add(&async self.findNativeCrtDirWindows(allocator, sdk));
+                        try batch.wait();
                     },
-                    c.ZigFindWindowsSdkError.OutOfMemory => return error.OutOfMemory,
-                    c.ZigFindWindowsSdkError.NotFound => return error.NotFound,
-                    c.ZigFindWindowsSdkError.PathTooLong => return error.NotFound,
+                    .OutOfMemory => return error.OutOfMemory,
+                    .NotFound => return error.NotFound,
+                    .PathTooLong => return error.NotFound,
                 }
-            },
-            .linux => {
-                try group.call(findNativeIncludeDirLinux, .{ self, allocator });
-                try group.call(findNativeLibDirLinux, .{ self, allocator });
-                try group.call(findNativeStaticLibDir, .{ self, allocator });
-                try group.call(findNativeDynamicLinker, .{ self, allocator });
-            },
-            .macosx, .freebsd, .netbsd => {
-                self.include_dir = try std.mem.dupe(allocator, u8, "/usr/include");
-            },
-            else => @compileError("unimplemented: find libc for this OS"),
+            }
+        } else {
+            var batch = Batch(FindError!void, 2, .auto_async).init();
+            batch.add(&async self.findNativeIncludeDirPosix(allocator));
+            if (is_freebsd or is_netbsd) {
+                self.crt_dir = try std.mem.dupeZ(allocator, u8, "/usr/lib");
+            } else if (is_linux or is_dragonfly) {
+                batch.add(&async self.findNativeCrtDirPosix(allocator));
+            }
+            try batch.wait();
         }
-        return group.wait();
+        return self;
     }
 
-    async fn findNativeIncludeDirLinux(self: *LibCInstallation, allocator: *Allocator) FindError!void {
-        const cc_exe = std.os.getenv("CC") orelse "cc";
+    /// Must be the same allocator passed to `parse` or `findNative`.
+    pub fn deinit(self: *LibCInstallation, allocator: *Allocator) void {
+        const fields = std.meta.fields(LibCInstallation);
+        inline for (fields) |field| {
+            if (@field(self, field.name)) |payload| {
+                allocator.free(payload);
+            }
+        }
+        self.* = undefined;
+    }
+
+    fn findNativeIncludeDirPosix(self: *LibCInstallation, allocator: *Allocator) FindError!void {
+        const dev_null = if (is_windows) "nul" else "/dev/null";
+        const cc_exe = std.os.getenvZ("CC") orelse default_cc_exe;
         const argv = [_][]const u8{
             cc_exe,
             "-E",
             "-Wp,-v",
             "-xc",
-            "/dev/null",
+            dev_null,
         };
-        // TODO make this use event loop
-        const errorable_result = std.ChildProcess.exec(allocator, &argv, null, null, 1024 * 1024);
-        const exec_result = if (std.debug.runtime_safety) blk: {
-            break :blk errorable_result catch unreachable;
-        } else blk: {
-            break :blk errorable_result catch |err| switch (err) {
-                error.OutOfMemory => return error.OutOfMemory,
-                else => return error.UnableToSpawnCCompiler,
-            };
+        const max_bytes = 1024 * 1024;
+        const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) {
+            error.OutOfMemory => return error.OutOfMemory,
+            else => return error.UnableToSpawnCCompiler,
         };
         defer {
-            allocator.free(exec_result.stdout);
-            allocator.free(exec_result.stderr);
+            allocator.free(exec_res.stdout);
+            allocator.free(exec_res.stderr);
         }
-
-        switch (exec_result.term) {
-            .Exited => |code| {
-                if (code != 0) return error.CCompilerExitCode;
-            },
-            else => {
-                return error.CCompilerCrashed;
-            },
+        switch (exec_res.term) {
+            .Exited => |code| if (code != 0) return error.CCompilerExitCode,
+            else => return error.CCompilerCrashed,
         }
 
-        var it = std.mem.tokenize(exec_result.stderr, "\n\r");
+        var it = std.mem.tokenize(exec_res.stderr, "\n\r");
         var search_paths = std.ArrayList([]const u8).init(allocator);
         defer search_paths.deinit();
         while (it.next()) |line| {
@@ -226,16 +263,44 @@ pub const LibCInstallation = struct {
             return error.CCompilerCannotFindHeaders;
         }
 
-        // search in reverse order
+        const include_dir_example_file = "stdlib.h";
+        const sys_include_dir_example_file = if (is_windows) "sys\\types.h" else "sys/errno.h";
+
         var path_i: usize = 0;
         while (path_i < search_paths.len) : (path_i += 1) {
+            // search in reverse order
             const search_path_untrimmed = search_paths.at(search_paths.len - path_i - 1);
             const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " ");
-            const stdlib_path = try fs.path.join(allocator, &[_][]const u8{ search_path, "stdlib.h" });
-            defer allocator.free(stdlib_path);
+            var search_dir = fs.cwd().openDirList(search_path) catch |err| switch (err) {
+                error.FileNotFound,
+                error.NotDir,
+                error.NoDevice,
+                => continue,
 
-            if (try fileExists(stdlib_path)) {
-                self.include_dir = try std.mem.dupe(allocator, u8, search_path);
+                else => return error.FileSystem,
+            };
+            defer search_dir.close();
+
+            if (self.include_dir == null) {
+                if (search_dir.accessZ(include_dir_example_file, .{})) |_| {
+                    self.include_dir = try std.mem.dupeZ(allocator, u8, search_path);
+                } else |err| switch (err) {
+                    error.FileNotFound => {},
+                    else => return error.FileSystem,
+                }
+            }
+
+            if (self.sys_include_dir == null) {
+                if (search_dir.accessZ(sys_include_dir_example_file, .{})) |_| {
+                    self.sys_include_dir = try std.mem.dupeZ(allocator, u8, search_path);
+                } else |err| switch (err) {
+                    error.FileNotFound => {},
+                    else => return error.FileSystem,
+                }
+            }
+
+            if (self.include_dir != null and self.sys_include_dir != null) {
+                // Success.
                 return;
             }
         }
@@ -243,7 +308,7 @@ pub const LibCInstallation = struct {
         return error.LibCStdLibHeaderNotFound;
     }
 
-    async fn findNativeIncludeDirWindows(self: *LibCInstallation, allocator: *Allocator, sdk: *c.ZigWindowsSDK) !void {
+    fn findNativeIncludeDirWindows(self: *LibCInstallation, allocator: *Allocator, sdk: *ZigWindowsSDK) !void {
         var search_buf: [2]Search = undefined;
         const searches = fillSearch(&search_buf, sdk);
 
@@ -255,179 +320,152 @@ pub const LibCInstallation = struct {
             const stream = &std.io.BufferOutStream.init(&result_buf).stream;
             try stream.print("{}\\Include\\{}\\ucrt", .{ search.path, search.version });
 
-            const stdlib_path = try fs.path.join(
-                allocator,
-                [_][]const u8{ result_buf.toSliceConst(), "stdlib.h" },
-            );
-            defer allocator.free(stdlib_path);
+            var dir = fs.cwd().openDirList(result_buf.toSliceConst()) catch |err| switch (err) {
+                error.FileNotFound,
+                error.NotDir,
+                error.NoDevice,
+                => continue,
 
-            if (try fileExists(stdlib_path)) {
-                self.include_dir = result_buf.toOwnedSlice();
-                return;
-            }
+                else => return error.FileSystem,
+            };
+            defer dir.close();
+
+            dir.accessZ("stdlib.h", .{}) catch |err| switch (err) {
+                error.FileNotFound => continue,
+                else => return error.FileSystem,
+            };
+
+            self.include_dir = result_buf.toOwnedSlice();
+            return;
         }
 
         return error.LibCStdLibHeaderNotFound;
     }
 
-    async fn findNativeLibDirWindows(self: *LibCInstallation, allocator: *Allocator, sdk: *c.ZigWindowsSDK) FindError!void {
+    fn findNativeCrtDirWindows(self: *LibCInstallation, allocator: *Allocator, sdk: *ZigWindowsSDK) FindError!void {
         var search_buf: [2]Search = undefined;
         const searches = fillSearch(&search_buf, sdk);
 
         var result_buf = try std.Buffer.initSize(allocator, 0);
         defer result_buf.deinit();
 
+        const arch_sub_dir = switch (builtin.arch) {
+            .i386 => "x86",
+            .x86_64 => "x64",
+            .arm, .armeb => "arm",
+            else => return error.UnsupportedArchitecture,
+        };
+
         for (searches) |search| {
             result_buf.shrink(0);
             const stream = &std.io.BufferOutStream.init(&result_buf).stream;
-            try stream.print("{}\\Lib\\{}\\ucrt\\", .{ search.path, search.version });
-            switch (builtin.arch) {
-                .i386 => try stream.write("x86"),
-                .x86_64 => try stream.write("x64"),
-                .aarch64 => try stream.write("arm"),
-                else => return error.UnsupportedArchitecture,
-            }
-            const ucrt_lib_path = try fs.path.join(
-                allocator,
-                [_][]const u8{ result_buf.toSliceConst(), "ucrt.lib" },
-            );
-            defer allocator.free(ucrt_lib_path);
-            if (try fileExists(ucrt_lib_path)) {
-                self.lib_dir = result_buf.toOwnedSlice();
-                return;
-            }
-        }
-        return error.LibCRuntimeNotFound;
-    }
+            try stream.print("{}\\Lib\\{}\\ucrt\\{}", .{ search.path, search.version, arch_sub_dir });
 
-    async fn findNativeLibDirLinux(self: *LibCInstallation, allocator: *Allocator) FindError!void {
-        self.lib_dir = try ccPrintFileName(allocator, "crt1.o", true);
-    }
+            var dir = fs.cwd().openDirList(result_buf.toSliceConst()) catch |err| switch (err) {
+                error.FileNotFound,
+                error.NotDir,
+                error.NoDevice,
+                => continue,
 
-    async fn findNativeStaticLibDir(self: *LibCInstallation, allocator: *Allocator) FindError!void {
-        self.static_lib_dir = try ccPrintFileName(allocator, "crtbegin.o", true);
-    }
+                else => return error.FileSystem,
+            };
+            defer dir.close();
 
-    async fn findNativeDynamicLinker(self: *LibCInstallation, allocator: *Allocator) FindError!void {
-        var dyn_tests = [_]DynTest{
-            DynTest{
-                .name = "ld-linux-x86-64.so.2",
-                .result = null,
-            },
-            DynTest{
-                .name = "ld-musl-x86_64.so.1",
-                .result = null,
-            },
-        };
-        var group = event.Group(FindError!void).init(allocator);
-        errdefer group.wait() catch {};
-        for (dyn_tests) |*dyn_test| {
-            try group.call(testNativeDynamicLinker, .{ self, allocator, dyn_test });
-        }
-        try group.wait();
-        for (dyn_tests) |*dyn_test| {
-            if (dyn_test.result) |result| {
-                self.dynamic_linker_path = result;
-                return;
-            }
+            dir.accessZ("ucrt.lib", .{}) catch |err| switch (err) {
+                error.FileNotFound => continue,
+                else => return error.FileSystem,
+            };
+
+            self.crt_dir = result_buf.toOwnedSlice();
+            return;
         }
+        return error.LibCRuntimeNotFound;
     }
 
-    const DynTest = struct {
-        name: []const u8,
-        result: ?[]const u8,
-    };
+    fn findNativeCrtDirPosix(self: *LibCInstallation, allocator: *Allocator) FindError!void {
+        self.crt_dir = try ccPrintFileName(allocator, "crt1.o", .only_dir);
+    }
 
-    async fn testNativeDynamicLinker(self: *LibCInstallation, allocator: *Allocator, dyn_test: *DynTest) FindError!void {
-        if (ccPrintFileName(allocator, dyn_test.name, false)) |result| {
-            dyn_test.result = result;
-            return;
-        } else |err| switch (err) {
-            error.LibCRuntimeNotFound => return,
-            else => return err,
-        }
+    fn findNativeStaticCrtDirPosix(self: *LibCInstallation, allocator: *Allocator) FindError!void {
+        self.static_crt_dir = try ccPrintFileName(allocator, "crtbegin.o", .only_dir);
     }
 
-    async fn findNativeKernel32LibDir(self: *LibCInstallation, allocator: *Allocator, sdk: *c.ZigWindowsSDK) FindError!void {
+    fn findNativeKernel32LibDir(self: *LibCInstallation, allocator: *Allocator, sdk: *ZigWindowsSDK) FindError!void {
         var search_buf: [2]Search = undefined;
         const searches = fillSearch(&search_buf, sdk);
 
         var result_buf = try std.Buffer.initSize(allocator, 0);
         defer result_buf.deinit();
 
+        const arch_sub_dir = switch (builtin.arch) {
+            .i386 => "x86",
+            .x86_64 => "x64",
+            .arm, .armeb => "arm",
+            else => return error.UnsupportedArchitecture,
+        };
+
         for (searches) |search| {
             result_buf.shrink(0);
             const stream = &std.io.BufferOutStream.init(&result_buf).stream;
-            try stream.print("{}\\Lib\\{}\\um\\", .{ search.path, search.version });
-            switch (builtin.arch) {
-                .i386 => try stream.write("x86\\"),
-                .x86_64 => try stream.write("x64\\"),
-                .aarch64 => try stream.write("arm\\"),
-                else => return error.UnsupportedArchitecture,
-            }
-            const kernel32_path = try fs.path.join(
-                allocator,
-                [_][]const u8{ result_buf.toSliceConst(), "kernel32.lib" },
-            );
-            defer allocator.free(kernel32_path);
-            if (try fileExists(kernel32_path)) {
-                self.kernel32_lib_dir = result_buf.toOwnedSlice();
-                return;
-            }
+            try stream.print("{}\\Lib\\{}\\um\\{}", .{ search.path, search.version, arch_sub_dir });
+
+            var dir = fs.cwd().openDirList(result_buf.toSliceConst()) catch |err| switch (err) {
+                error.FileNotFound,
+                error.NotDir,
+                error.NoDevice,
+                => continue,
+
+                else => return error.FileSystem,
+            };
+            defer dir.close();
+
+            dir.accessZ("kernel32.lib", .{}) catch |err| switch (err) {
+                error.FileNotFound => continue,
+                else => return error.FileSystem,
+            };
+
+            self.kernel32_lib_dir = result_buf.toOwnedSlice();
+            return;
         }
         return error.LibCKernel32LibNotFound;
     }
-
-    fn initEmpty(self: *LibCInstallation) void {
-        self.* = LibCInstallation{
-            .include_dir = @as([*]const u8, undefined)[0..0],
-            .lib_dir = null,
-            .static_lib_dir = null,
-            .msvc_lib_dir = null,
-            .kernel32_lib_dir = null,
-            .dynamic_linker_path = null,
-        };
-    }
 };
 
+const default_cc_exe = if (is_windows) "cc.exe" else "cc";
+
 /// caller owns returned memory
-fn ccPrintFileName(allocator: *Allocator, o_file: []const u8, want_dirname: bool) ![]u8 {
-    const cc_exe = std.os.getenv("CC") orelse "cc";
+pub fn ccPrintFileName(
+    allocator: *Allocator,
+    o_file: []const u8,
+    want_dirname: enum { full_path, only_dir },
+) ![:0]u8 {
+    const cc_exe = std.os.getenvZ("CC") orelse default_cc_exe;
     const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={}", .{o_file});
     defer allocator.free(arg1);
     const argv = [_][]const u8{ cc_exe, arg1 };
 
-    // TODO This simulates evented I/O for the child process exec
-    event.Loop.startCpuBoundOperation();
-    const errorable_result = std.ChildProcess.exec(allocator, &argv, null, null, 1024 * 1024);
-    const exec_result = if (std.debug.runtime_safety) blk: {
-        break :blk errorable_result catch unreachable;
-    } else blk: {
-        break :blk errorable_result catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            else => return error.UnableToSpawnCCompiler,
-        };
+    const max_bytes = 1024 * 1024;
+    const exec_res = std.ChildProcess.exec(allocator, &argv, null, null, max_bytes) catch |err| switch (err) {
+        error.OutOfMemory => return error.OutOfMemory,
+        else => return error.UnableToSpawnCCompiler,
     };
     defer {
-        allocator.free(exec_result.stdout);
-        allocator.free(exec_result.stderr);
+        allocator.free(exec_res.stdout);
+        allocator.free(exec_res.stderr);
     }
-    switch (exec_result.term) {
-        .Exited => |code| {
-            if (code != 0) return error.CCompilerExitCode;
-        },
-        else => {
-            return error.CCompilerCrashed;
-        },
+    switch (exec_res.term) {
+        .Exited => |code| if (code != 0) return error.CCompilerExitCode,
+        else => return error.CCompilerCrashed,
     }
-    var it = std.mem.tokenize(exec_result.stdout, "\n\r");
-    const line = it.next() orelse return error.LibCRuntimeNotFound;
-    const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;
 
-    if (want_dirname) {
-        return std.mem.dupe(allocator, u8, dirname);
-    } else {
-        return std.mem.dupe(allocator, u8, line);
+    var it = std.mem.tokenize(exec_res.stdout, "\n\r");
+    const line = it.next() orelse return error.LibCRuntimeNotFound;
+    switch (want_dirname) {
+        .full_path => return std.mem.dupeZ(allocator, u8, line),
+        .only_dir => {
+            const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;
+            return std.mem.dupeZ(allocator, u8, dirname);
+        },
     }
 }
 
@@ -436,34 +474,25 @@ const Search = struct {
     version: []const u8,
 };
 
-fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
+fn fillSearch(search_buf: *[2]Search, sdk: *ZigWindowsSDK) []Search {
     var search_end: usize = 0;
-    if (sdk.path10_ptr != 0) {
-        if (sdk.version10_ptr != 0) {
+    if (sdk.path10_ptr) |path10_ptr| {
+        if (sdk.version10_ptr) |version10_ptr| {
             search_buf[search_end] = Search{
-                .path = sdk.path10_ptr[0..sdk.path10_len],
-                .version = sdk.version10_ptr[0..sdk.version10_len],
+                .path = path10_ptr[0..sdk.path10_len],
+                .version = version10_ptr[0..sdk.version10_len],
             };
             search_end += 1;
         }
     }
-    if (sdk.path81_ptr != 0) {
-        if (sdk.version81_ptr != 0) {
+    if (sdk.path81_ptr) |path81_ptr| {
+        if (sdk.version81_ptr) |version81_ptr| {
             search_buf[search_end] = Search{
-                .path = sdk.path81_ptr[0..sdk.path81_len],
-                .version = sdk.version81_ptr[0..sdk.version81_len],
+                .path = path81_ptr[0..sdk.path81_len],
+                .version = version81_ptr[0..sdk.version81_len],
             };
             search_end += 1;
         }
     }
     return search_buf[0..search_end];
 }
-
-fn fileExists(path: []const u8) !bool {
-    if (fs.File.access(path)) |_| {
-        return true;
-    } else |err| switch (err) {
-        error.FileNotFound => return false,
-        else => return error.FileSystem,
-    }
-}
src-self-hosted/stage1.zig
@@ -14,6 +14,7 @@ const self_hosted_main = @import("main.zig");
 const errmsg = @import("errmsg.zig");
 const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer;
 const assert = std.debug.assert;
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 var stderr_file: fs.File = undefined;
 var stderr: *io.OutStream(fs.File.WriteError) = undefined;
@@ -93,6 +94,20 @@ const Error = extern enum {
     InvalidLlvmCpuFeaturesFormat,
     UnknownApplicationBinaryInterface,
     ASTUnitFailure,
+    BadPathName,
+    SymLinkLoop,
+    ProcessFdQuotaExceeded,
+    SystemFdQuotaExceeded,
+    NoDevice,
+    DeviceBusy,
+    UnableToSpawnCCompiler,
+    CCompilerExitCode,
+    CCompilerCrashed,
+    CCompilerCannotFindHeaders,
+    LibCRuntimeNotFound,
+    LibCStdLibHeaderNotFound,
+    LibCKernel32LibNotFound,
+    UnsupportedArchitecture,
 };
 
 const FILE = std.c.FILE;
@@ -113,12 +128,12 @@ export fn stage2_translate_c(
         error.SemanticAnalyzeFail => {
             out_errors_ptr.* = errors.ptr;
             out_errors_len.* = errors.len;
-            return Error.CCompileErrors;
+            return .CCompileErrors;
         },
-        error.ASTUnitFailure => return Error.ASTUnitFailure,
-        error.OutOfMemory => return Error.OutOfMemory,
+        error.ASTUnitFailure => return .ASTUnitFailure,
+        error.OutOfMemory => return .OutOfMemory,
     };
-    return Error.None;
+    return .None;
 }
 
 export fn stage2_free_clang_errors(errors_ptr: [*]translate_c.ClangErrMsg, errors_len: usize) void {
@@ -129,18 +144,18 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
     const c_out_stream = &std.io.COutStream.init(output_file).stream;
     _ = std.zig.render(std.heap.c_allocator, c_out_stream, tree) catch |e| switch (e) {
         error.WouldBlock => unreachable, // stage1 opens stuff in exclusively blocking mode
-        error.SystemResources => return Error.SystemResources,
-        error.OperationAborted => return Error.OperationAborted,
-        error.BrokenPipe => return Error.BrokenPipe,
-        error.DiskQuota => return Error.DiskQuota,
-        error.FileTooBig => return Error.FileTooBig,
-        error.NoSpaceLeft => return Error.NoSpaceLeft,
-        error.AccessDenied => return Error.AccessDenied,
-        error.OutOfMemory => return Error.OutOfMemory,
-        error.Unexpected => return Error.Unexpected,
-        error.InputOutput => return Error.FileSystem,
+        error.SystemResources => return .SystemResources,
+        error.OperationAborted => return .OperationAborted,
+        error.BrokenPipe => return .BrokenPipe,
+        error.DiskQuota => return .DiskQuota,
+        error.FileTooBig => return .FileTooBig,
+        error.NoSpaceLeft => return .NoSpaceLeft,
+        error.AccessDenied => return .AccessDenied,
+        error.OutOfMemory => return .OutOfMemory,
+        error.Unexpected => return .Unexpected,
+        error.InputOutput => return .FileSystem,
     };
-    return Error.None;
+    return .None;
 }
 
 // TODO: just use the actual self-hosted zig fmt. Until https://github.com/ziglang/zig/issues/2377,
@@ -832,3 +847,187 @@ export fn stage2_cpu_features_get_llvm_cpu(cpu_features: *const Stage2CpuFeature
 export fn stage2_cpu_features_get_llvm_features(cpu_features: *const Stage2CpuFeatures) ?[*:0]const u8 {
     return cpu_features.llvm_features_str;
 }
+
+// ABI warning
+const Stage2LibCInstallation = extern struct {
+    include_dir: [*:0]const u8,
+    include_dir_len: usize,
+    sys_include_dir: [*:0]const u8,
+    sys_include_dir_len: usize,
+    crt_dir: [*:0]const u8,
+    crt_dir_len: usize,
+    static_crt_dir: [*:0]const u8,
+    static_crt_dir_len: usize,
+    msvc_lib_dir: [*:0]const u8,
+    msvc_lib_dir_len: usize,
+    kernel32_lib_dir: [*:0]const u8,
+    kernel32_lib_dir_len: usize,
+
+    fn initFromStage2(self: *Stage2LibCInstallation, libc: LibCInstallation) void {
+        if (libc.include_dir) |s| {
+            self.include_dir = s.ptr;
+            self.include_dir_len = s.len;
+        } else {
+            self.include_dir = "";
+            self.include_dir_len = 0;
+        }
+        if (libc.sys_include_dir) |s| {
+            self.sys_include_dir = s.ptr;
+            self.sys_include_dir_len = s.len;
+        } else {
+            self.sys_include_dir = "";
+            self.sys_include_dir_len = 0;
+        }
+        if (libc.crt_dir) |s| {
+            self.crt_dir = s.ptr;
+            self.crt_dir_len = s.len;
+        } else {
+            self.crt_dir = "";
+            self.crt_dir_len = 0;
+        }
+        if (libc.static_crt_dir) |s| {
+            self.static_crt_dir = s.ptr;
+            self.static_crt_dir_len = s.len;
+        } else {
+            self.static_crt_dir = "";
+            self.static_crt_dir_len = 0;
+        }
+        if (libc.msvc_lib_dir) |s| {
+            self.msvc_lib_dir = s.ptr;
+            self.msvc_lib_dir_len = s.len;
+        } else {
+            self.msvc_lib_dir = "";
+            self.msvc_lib_dir_len = 0;
+        }
+        if (libc.kernel32_lib_dir) |s| {
+            self.kernel32_lib_dir = s.ptr;
+            self.kernel32_lib_dir_len = s.len;
+        } else {
+            self.kernel32_lib_dir = "";
+            self.kernel32_lib_dir_len = 0;
+        }
+    }
+
+    fn toStage2(self: Stage2LibCInstallation) LibCInstallation {
+        var libc: LibCInstallation = .{};
+        if (self.include_dir_len != 0) {
+            libc.include_dir = self.include_dir[0..self.include_dir_len :0];
+        }
+        if (self.sys_include_dir_len != 0) {
+            libc.sys_include_dir = self.sys_include_dir[0..self.sys_include_dir_len :0];
+        }
+        if (self.crt_dir_len != 0) {
+            libc.crt_dir = self.crt_dir[0..self.crt_dir_len :0];
+        }
+        if (self.static_crt_dir_len != 0) {
+            libc.static_crt_dir = self.static_crt_dir[0..self.static_crt_dir_len :0];
+        }
+        if (self.msvc_lib_dir_len != 0) {
+            libc.msvc_lib_dir = self.msvc_lib_dir[0..self.msvc_lib_dir_len :0];
+        }
+        if (self.kernel32_lib_dir_len != 0) {
+            libc.kernel32_lib_dir = self.kernel32_lib_dir[0..self.kernel32_lib_dir_len :0];
+        }
+        return libc;
+    }
+};
+
+// ABI warning
+export fn stage2_libc_parse(stage1_libc: *Stage2LibCInstallation, libc_file_z: [*:0]const u8) Error {
+    stderr_file = std.io.getStdErr();
+    stderr = &stderr_file.outStream().stream;
+    const libc_file = mem.toSliceConst(u8, libc_file_z);
+    var libc = LibCInstallation.parse(std.heap.c_allocator, libc_file, stderr) catch |err| switch (err) {
+        error.ParseError => return .SemanticAnalyzeFail,
+        error.DiskQuota => return .DiskQuota,
+        error.FileTooBig => return .FileTooBig,
+        error.InputOutput => return .FileSystem,
+        error.NoSpaceLeft => return .NoSpaceLeft,
+        error.AccessDenied => return .AccessDenied,
+        error.BrokenPipe => return .BrokenPipe,
+        error.SystemResources => return .SystemResources,
+        error.OperationAborted => return .OperationAborted,
+        error.WouldBlock => unreachable,
+        error.Unexpected => return .Unexpected,
+        error.EndOfStream => return .EndOfFile,
+        error.IsDir => return .IsDir,
+        error.ConnectionResetByPeer => unreachable,
+        error.OutOfMemory => return .OutOfMemory,
+        error.Unseekable => unreachable,
+        error.SharingViolation => return .SharingViolation,
+        error.PathAlreadyExists => unreachable,
+        error.FileNotFound => return .FileNotFound,
+        error.PipeBusy => return .PipeBusy,
+        error.NameTooLong => return .PathTooLong,
+        error.InvalidUtf8 => return .BadPathName,
+        error.BadPathName => return .BadPathName,
+        error.SymLinkLoop => return .SymLinkLoop,
+        error.ProcessFdQuotaExceeded => return .ProcessFdQuotaExceeded,
+        error.SystemFdQuotaExceeded => return .SystemFdQuotaExceeded,
+        error.NoDevice => return .NoDevice,
+        error.NotDir => return .NotDir,
+        error.DeviceBusy => return .DeviceBusy,
+    };
+    stage1_libc.initFromStage2(libc);
+    return .None;
+}
+
+// ABI warning
+export fn stage2_libc_find_native(stage1_libc: *Stage2LibCInstallation) Error {
+    var libc = LibCInstallation.findNative(std.heap.c_allocator) catch |err| switch (err) {
+        error.OutOfMemory => return .OutOfMemory,
+        error.FileSystem => return .FileSystem,
+        error.UnableToSpawnCCompiler => return .UnableToSpawnCCompiler,
+        error.CCompilerExitCode => return .CCompilerExitCode,
+        error.CCompilerCrashed => return .CCompilerCrashed,
+        error.CCompilerCannotFindHeaders => return .CCompilerCannotFindHeaders,
+        error.LibCRuntimeNotFound => return .LibCRuntimeNotFound,
+        error.LibCStdLibHeaderNotFound => return .LibCStdLibHeaderNotFound,
+        error.LibCKernel32LibNotFound => return .LibCKernel32LibNotFound,
+        error.UnsupportedArchitecture => return .UnsupportedArchitecture,
+    };
+    stage1_libc.initFromStage2(libc);
+    return .None;
+}
+
+// ABI warning
+export fn stage2_libc_render(stage1_libc: *Stage2LibCInstallation, output_file: *FILE) Error {
+    var libc = stage1_libc.toStage2();
+    const c_out_stream = &std.io.COutStream.init(output_file).stream;
+    libc.render(c_out_stream) catch |err| switch (err) {
+        error.WouldBlock => unreachable, // stage1 opens stuff in exclusively blocking mode
+        error.SystemResources => return .SystemResources,
+        error.OperationAborted => return .OperationAborted,
+        error.BrokenPipe => return .BrokenPipe,
+        error.DiskQuota => return .DiskQuota,
+        error.FileTooBig => return .FileTooBig,
+        error.NoSpaceLeft => return .NoSpaceLeft,
+        error.AccessDenied => return .AccessDenied,
+        error.Unexpected => return .Unexpected,
+        error.InputOutput => return .FileSystem,
+    };
+    return .None;
+}
+
+// ABI warning
+export fn stage2_libc_cc_print_file_name(
+    out_ptr: *[*:0]u8,
+    out_len: *usize,
+    o_file: [*:0]const u8,
+    want_dirname: bool,
+) Error {
+    const result = @import("libc_installation.zig").ccPrintFileName(
+        std.heap.c_allocator,
+        mem.toSliceConst(u8, o_file),
+        if (want_dirname) .only_dir else .full_path,
+    ) catch |err| switch (err) {
+        error.OutOfMemory => return .OutOfMemory,
+        error.LibCRuntimeNotFound => return .FileNotFound,
+        error.CCompilerExitCode => return .CCompilerExitCode,
+        error.CCompilerCrashed => return .CCompilerCrashed,
+        error.UnableToSpawnCCompiler => return .UnableToSpawnCCompiler,
+    };
+    out_ptr.* = result.ptr;
+    out_len.* = result.len;
+    return .None;
+}
src-self-hosted/windows_sdk.zig
@@ -0,0 +1,22 @@
+// C API bindings for src/windows_sdk.h
+
+pub const ZigWindowsSDK = extern struct {
+    path10_ptr: ?[*]const u8,
+    path10_len: usize,
+    version10_ptr: ?[*]const u8,
+    version10_len: usize,
+    path81_ptr: ?[*]const u8,
+    path81_len: usize,
+    version81_ptr: ?[*]const u8,
+    version81_len: usize,
+    msvc_lib_dir_ptr: ?[*]const u8,
+    msvc_lib_dir_len: usize,
+};
+pub const ZigFindWindowsSdkError = extern enum {
+    None,
+    OutOfMemory,
+    NotFound,
+    PathTooLong,
+};
+pub extern fn zig_find_windows_sdk(out_sdk: **ZigWindowsSDK) ZigFindWindowsSdkError;
+pub extern fn zig_free_windows_sdk(sdk: *ZigWindowsSDK) void;
build.zig
@@ -175,7 +175,7 @@ fn dependOnLib(b: *Builder, lib_exe_obj: var, dep: LibraryDep) void {
 }
 
 fn fileExists(filename: []const u8) !bool {
-    fs.File.access(filename) catch |err| switch (err) {
+    fs.cwd().access(filename, .{}) catch |err| switch (err) {
         error.FileNotFound => return false,
         else => return err,
     };
CMakeLists.txt
@@ -457,7 +457,6 @@ set(ZIG_SOURCES
     "${CMAKE_SOURCE_DIR}/src/heap.cpp"
     "${CMAKE_SOURCE_DIR}/src/ir.cpp"
     "${CMAKE_SOURCE_DIR}/src/ir_print.cpp"
-    "${CMAKE_SOURCE_DIR}/src/libc_installation.cpp"
     "${CMAKE_SOURCE_DIR}/src/link.cpp"
     "${CMAKE_SOURCE_DIR}/src/mem.cpp"
     "${CMAKE_SOURCE_DIR}/src/os.cpp"