Commit 066864a0bf

Andrew Kelley <andrew@ziglang.org>
2025-10-07 03:34:51
std.zig.system: upgrade to std.Io.Reader
1 parent b428612
lib/compiler/build_runner.zig
@@ -38,6 +38,10 @@ pub fn main() !void {
 
     const args = try process.argsAlloc(arena);
 
+    var threaded: std.Io.Threaded = .init(gpa);
+    defer threaded.deinit();
+    const io = threaded.io();
+
     // skip my own exe name
     var arg_idx: usize = 1;
 
@@ -68,6 +72,7 @@ pub fn main() !void {
     };
 
     var graph: std.Build.Graph = .{
+        .io = io,
         .arena = arena,
         .cache = .{
             .gpa = arena,
lib/std/Build/Step/Options.zig
@@ -532,6 +532,8 @@ const Arg = struct {
 test Options {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
+    const io = std.testing.io;
+
     var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
     defer arena.deinit();
 
@@ -546,7 +548,7 @@ test Options {
         .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },
         .host = .{
             .query = .{},
-            .result = try std.zig.system.resolveTargetQuery(.{}),
+            .result = try std.zig.system.resolveTargetQuery(io, .{}),
         },
         .zig_lib_directory = std.Build.Cache.Directory.cwd(),
         .time_report = false,
lib/std/Build/WebServer.zig
@@ -516,6 +516,7 @@ pub fn serveTarFile(
 }
 
 fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
+    const io = ws.graph.io;
     const root_name = "build-web";
     const arch_os_abi = "wasm32-freestanding";
     const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
@@ -659,7 +660,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
     };
     const bin_name = try std.zig.binNameAlloc(arena, .{
         .root_name = root_name,
-        .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{
+        .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
             .arch_os_abi = arch_os_abi,
             .cpu_features = cpu_features,
         }) catch unreachable) catch unreachable),
lib/std/Io/net.zig
@@ -228,7 +228,7 @@ pub const IpAddress = union(enum) {
     ///
     /// One bound `Socket` can be used to receive messages from multiple
     /// different addresses.
-    pub fn bind(address: IpAddress, io: Io, options: BindOptions) BindError!Socket {
+    pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {
         return io.vtable.ipBind(io.userdata, address, options);
     }
 
lib/std/Io/Threaded.zig
@@ -1054,7 +1054,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!
 fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
     const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
-        .none => .monotonic,
+        .none => .awake,
         .duration => |d| d.clock,
         .deadline => |d| d.clock,
     });
@@ -1087,7 +1087,6 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
     const ms = ms: {
         const duration_and_clock = (try timeout.toDurationFromNow(pool.io())) orelse
             break :ms std.math.maxInt(windows.DWORD);
-        if (duration_and_clock.clock != .monotonic) return error.UnsupportedClock;
         break :ms std.math.lossyCast(windows.DWORD, duration_and_clock.duration.toMilliseconds());
     };
     windows.kernel32.Sleep(ms);
@@ -1132,8 +1131,6 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
             .sec = std.math.maxInt(sec_type),
             .nsec = std.math.maxInt(nsec_type),
         };
-        // TODO check which clock nanosleep uses on this host
-        // and return error.UnsupportedClock if it does not match
         const ns = d.duration.nanoseconds;
         break :t .{
             .sec = @intCast(@divFloor(ns, std.time.ns_per_s)),
@@ -2046,9 +2043,9 @@ fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t {
 fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t {
     return switch (clock) {
         .realtime => .REALTIME,
-        .monotonic => .MONOTONIC,
-        .uptime => .MONOTONIC,
-        .process_cputime_id => .PROCESS_CPUTIME_ID,
-        .thread_cputime_id => .THREAD_CPUTIME_ID,
+        .awake => .MONOTONIC,
+        .boot => .MONOTONIC,
+        .cpu_process => .PROCESS_CPUTIME_ID,
+        .cpu_thread => .THREAD_CPUTIME_ID,
     };
 }
lib/std/Target/Query.zig
@@ -612,6 +612,8 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool {
 }
 
 test parse {
+    const io = std.testing.io;
+
     if (builtin.target.isGnuLibC()) {
         var query = try Query.parse(.{});
         query.setGnuLibCVersion(2, 1, 1);
@@ -654,7 +656,7 @@ test parse {
             .arch_os_abi = "x86_64-linux-gnu",
             .cpu_features = "x86_64-sse-sse2-avx-cx8",
         });
-        const target = try std.zig.system.resolveTargetQuery(query);
+        const target = try std.zig.system.resolveTargetQuery(io, query);
 
         try std.testing.expect(target.os.tag == .linux);
         try std.testing.expect(target.abi == .gnu);
@@ -679,7 +681,7 @@ test parse {
             .arch_os_abi = "arm-linux-musleabihf",
             .cpu_features = "generic+v8a",
         });
-        const target = try std.zig.system.resolveTargetQuery(query);
+        const target = try std.zig.system.resolveTargetQuery(io, query);
 
         try std.testing.expect(target.os.tag == .linux);
         try std.testing.expect(target.abi == .musleabihf);
@@ -696,7 +698,7 @@ test parse {
             .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
             .cpu_features = "generic+v8a",
         });
-        const target = try std.zig.system.resolveTargetQuery(query);
+        const target = try std.zig.system.resolveTargetQuery(io, query);
 
         try std.testing.expect(target.cpu.arch == .aarch64);
         try std.testing.expect(target.os.tag == .linux);
@@ -719,7 +721,7 @@ test parse {
         const query = try Query.parse(.{
             .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30",
         });
-        const target = try std.zig.system.resolveTargetQuery(query);
+        const target = try std.zig.system.resolveTargetQuery(io, query);
 
         try std.testing.expect(target.cpu.arch == .aarch64);
         try std.testing.expect(target.os.tag == .linux);
@@ -740,7 +742,7 @@ test parse {
         const query = try Query.parse(.{
             .arch_os_abi = "x86-windows.xp...win8-msvc",
         });
-        const target = try std.zig.system.resolveTargetQuery(query);
+        const target = try std.zig.system.resolveTargetQuery(io, query);
 
         try std.testing.expect(target.cpu.arch == .x86);
         try std.testing.expect(target.os.tag == .windows);
lib/std/zig/system.zig
@@ -1,3 +1,14 @@
+const builtin = @import("builtin");
+const std = @import("../std.zig");
+const mem = std.mem;
+const elf = std.elf;
+const fs = std.fs;
+const assert = std.debug.assert;
+const Target = std.Target;
+const native_endian = builtin.cpu.arch.endian();
+const posix = std.posix;
+const Io = std.Io;
+
 pub const NativePaths = @import("system/NativePaths.zig");
 
 pub const windows = @import("system/windows.zig");
@@ -199,14 +210,14 @@ pub const DetectError = error{
     OSVersionDetectionFail,
     Unexpected,
     ProcessNotFound,
-};
+} || Io.Cancelable;
 
 /// Given a `Target.Query`, which specifies in detail which parts of the
 /// target should be detected natively, which should be standard or default,
 /// and which are provided explicitly, this function resolves the native
 /// components by detecting the native system, and then resolves
 /// standard/default parts relative to that.
-pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
+pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
     // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
     // native CPU architecture as being different than the current target), we use this:
     const query_cpu_arch = query.cpu_arch orelse builtin.cpu.arch;
@@ -411,7 +422,33 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
         query.cpu_features_sub,
     );
 
-    var result = try detectAbiAndDynamicLinker(cpu, os, query);
+    var result = detectAbiAndDynamicLinker(io, cpu, os, query) catch |err| switch (err) {
+        error.Canceled => |e| return e,
+        error.Unexpected => |e| return e,
+        error.WouldBlock => return error.Unexpected,
+        error.BrokenPipe => return error.Unexpected,
+        error.ConnectionResetByPeer => return error.Unexpected,
+        error.ConnectionTimedOut => return error.Unexpected,
+        error.NotOpenForReading => return error.Unexpected,
+        error.SocketUnconnected => return error.Unexpected,
+
+        error.AccessDenied,
+        error.ProcessNotFound,
+        error.SymLinkLoop,
+        error.ProcessFdQuotaExceeded,
+        error.SystemFdQuotaExceeded,
+        error.SystemResources,
+        error.IsDir,
+        error.DeviceBusy,
+        error.InputOutput,
+        error.LockViolation,
+
+        error.UnableToOpenElfFile,
+        error.UnhelpfulFile,
+        error.InvalidElfFile,
+        error.RelativeShebang,
+        => return defaultAbiAndDynamicLinker(cpu, os, query),
+    };
 
     // These CPU feature hacks have to come after ABI detection.
     {
@@ -505,54 +542,16 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T
     return null;
 }
 
-pub const AbiAndDynamicLinkerFromFileError = error{
-    FileSystem,
-    SystemResources,
-    SymLinkLoop,
-    ProcessFdQuotaExceeded,
-    SystemFdQuotaExceeded,
-    UnableToReadElfFile,
-    InvalidElfClass,
-    InvalidElfVersion,
-    InvalidElfEndian,
-    InvalidElfFile,
-    InvalidElfMagic,
-    Unexpected,
-    UnexpectedEndOfFile,
-    NameTooLong,
-    ProcessNotFound,
-    StaticElfFile,
-};
+pub const AbiAndDynamicLinkerFromFileError = error{};
 
 pub fn abiAndDynamicLinkerFromFile(
-    file: fs.File,
+    file_reader: *Io.File.Reader,
+    header: *const elf.Header,
     cpu: Target.Cpu,
     os: Target.Os,
     ld_info_list: []const LdInfo,
     query: Target.Query,
 ) AbiAndDynamicLinkerFromFileError!Target {
-    var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
-    _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len);
-    const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf);
-    const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf);
-    if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
-    const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) {
-        elf.ELFDATA2LSB => .little,
-        elf.ELFDATA2MSB => .big,
-        else => return error.InvalidElfEndian,
-    };
-    const need_bswap = elf_endian != native_endian;
-    if (hdr32.e_ident[elf.EI.VERSION] != 1) return error.InvalidElfVersion;
-
-    const is_64 = switch (hdr32.e_ident[elf.EI.CLASS]) {
-        elf.ELFCLASS32 => false,
-        elf.ELFCLASS64 => true,
-        else => return error.InvalidElfClass,
-    };
-    var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
-    const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
-    const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
-
     var result: Target = .{
         .cpu = cpu,
         .os = os,
@@ -563,167 +562,87 @@ pub fn abiAndDynamicLinkerFromFile(
     var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
     const look_for_ld = query.dynamic_linker.get() == null;
 
-    var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
-    if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
-
-    var ph_i: u16 = 0;
     var got_dyn_section: bool = false;
-
-    while (ph_i < phnum) {
-        // Reserve some bytes so that we can deref the 64-bit struct fields
-        // even when the ELF file is 32-bits.
-        const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
-        const ph_read_byte_len = try preadAtLeast(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
-        var ph_buf_i: usize = 0;
-        while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
-            ph_i += 1;
-            phoff += phentsize;
-            ph_buf_i += phentsize;
-        }) {
-            const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
-            const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
-            const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
-            switch (p_type) {
-                elf.PT_INTERP => {
-                    got_dyn_section = true;
-
-                    if (look_for_ld) {
-                        const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
-                        const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
-                        if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
-                        const filesz: usize = @intCast(p_filesz);
-                        _ = try preadAtLeast(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
-                        // PT_INTERP includes a null byte in filesz.
-                        const len = filesz - 1;
-                        // dynamic_linker.max_byte is "max", not "len".
-                        // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
-                        result.dynamic_linker.len = @intCast(len);
-
-                        // Use it to determine ABI.
-                        const full_ld_path = result.dynamic_linker.buffer[0..len];
-                        for (ld_info_list) |ld_info| {
-                            const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
-                            if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
-                                result.abi = ld_info.abi;
-                                break;
-                            }
+    {
+        var it = header.iterateProgramHeaders(file_reader);
+        while (try it.next()) |phdr| switch (phdr.p_type) {
+            elf.PT_INTERP => {
+                got_dyn_section = true;
+
+                if (look_for_ld) {
+                    const p_filesz = phdr.p_filesz;
+                    if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
+                    const filesz: usize = @intCast(p_filesz);
+                    try file_reader.seekTo(phdr.p_offset);
+                    try file_reader.interface.readSliceAll(result.dynamic_linker.buffer[0..filesz]);
+                    // PT_INTERP includes a null byte in filesz.
+                    const len = filesz - 1;
+                    // dynamic_linker.max_byte is "max", not "len".
+                    // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
+                    result.dynamic_linker.len = @intCast(len);
+
+                    // Use it to determine ABI.
+                    const full_ld_path = result.dynamic_linker.buffer[0..len];
+                    for (ld_info_list) |ld_info| {
+                        const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
+                        if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
+                            result.abi = ld_info.abi;
+                            break;
                         }
                     }
-                },
-                // We only need this for detecting glibc version.
-                elf.PT_DYNAMIC => {
-                    got_dyn_section = true;
-
-                    if (builtin.target.os.tag == .linux and result.isGnuLibC() and
-                        query.glibc_version == null)
-                    {
-                        var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
-                        const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
-                        const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
-                        const dyn_num = p_filesz / dyn_size;
-                        var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
-                        var dyn_i: usize = 0;
-                        dyn: while (dyn_i < dyn_num) {
-                            // Reserve some bytes so that we can deref the 64-bit struct fields
-                            // even when the ELF file is 32-bits.
-                            const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
-                            const dyn_read_byte_len = try preadAtLeast(
-                                file,
-                                dyn_buf[0 .. dyn_buf.len - dyn_reserve],
-                                dyn_off,
-                                dyn_size,
-                            );
-                            var dyn_buf_i: usize = 0;
-                            while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
-                                dyn_i += 1;
-                                dyn_off += dyn_size;
-                                dyn_buf_i += dyn_size;
-                            }) {
-                                const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
-                                const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
-                                const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
-                                const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
-                                if (tag == elf.DT_RUNPATH) {
-                                    rpath_offset = val;
-                                    break :dyn;
-                                }
-                            }
+                }
+            },
+            // We only need this for detecting glibc version.
+            elf.PT_DYNAMIC => {
+                got_dyn_section = true;
+
+                if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
+                    var dyn_it = header.iterateDynamicSection(file_reader, phdr.p_offset, phdr.p_filesz);
+                    while (try dyn_it.next()) |dyn| {
+                        if (dyn.d_tag == elf.DT_RUNPATH) {
+                            rpath_offset = dyn.d_val;
+                            break;
                         }
                     }
-                },
-                else => continue,
-            }
-        }
+                }
+            },
+            else => continue,
+        };
     }
 
     if (!got_dyn_section) {
         return error.StaticElfFile;
     }
 
-    if (builtin.target.os.tag == .linux and result.isGnuLibC() and
-        query.glibc_version == null)
-    {
-        const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
-
-        var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
-        const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
-        const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
-
-        var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
-        if (sh_buf.len < shentsize) return error.InvalidElfFile;
-
-        _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize);
-        const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
-        const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
-        const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
-        const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
-        var strtab_buf: [4096:0]u8 = undefined;
-        const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
-        const shstrtab_read_len = try preadAtLeast(file, &strtab_buf, shstrtab_off, shstrtab_len);
-        const shstrtab = strtab_buf[0..shstrtab_read_len];
-
-        const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
-        var sh_i: u16 = 0;
-        const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
-            // Reserve some bytes so that we can deref the 64-bit struct fields
-            // even when the ELF file is 32-bits.
-            const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
-            const sh_read_byte_len = try preadAtLeast(
-                file,
-                sh_buf[0 .. sh_buf.len - sh_reserve],
-                shoff,
-                shentsize,
-            );
-            var sh_buf_i: usize = 0;
-            while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
-                sh_i += 1;
-                shoff += shentsize;
-                sh_buf_i += shentsize;
-            }) {
-                const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
-                const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
-                const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
-                const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
-                if (mem.eql(u8, sh_name, ".dynstr")) {
-                    break :find_dyn_str .{
-                        .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
-                        .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
-                    };
-                }
-            }
-        } else null;
-
+    if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
+        const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
+        try file_reader.seekTo(str_section_off);
+        const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
+        var strtab_buf: [4096]u8 = undefined;
+        const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
+        try file_reader.seekTo(shstr.sh_offset);
+        try file_reader.interface.readSliceAll(shstrtab);
+        const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: {
+            var it = header.iterateSectionHeaders(&file_reader.interface);
+            while (it.next()) |shdr| {
+                const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
+                const sh_name = shstrtab[shdr.sh_name..end :0];
+                if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
+                    .offset = shdr.sh_offset,
+                    .size = shdr.sh_size,
+                };
+            } else break :find_dyn_str null;
+        };
         if (dynstr) |ds| {
             if (rpath_offset) |rpoff| {
                 if (rpoff > ds.size) return error.InvalidElfFile;
                 const rpoff_file = ds.offset + rpoff;
                 const rp_max_size = ds.size - rpoff;
 
-                const strtab_len = @min(rp_max_size, strtab_buf.len);
-                const strtab_read_len = try preadAtLeast(file, &strtab_buf, rpoff_file, strtab_len);
-                const strtab = strtab_buf[0..strtab_read_len];
+                try file_reader.seekTo(rpoff_file);
+                const rpath_list = try file_reader.interface.takeSentinel(0);
+                if (rpath_list.len > rp_max_size) return error.StreamTooLong;
 
-                const rpath_list = mem.sliceTo(strtab, 0);
                 var it = mem.tokenizeScalar(u8, rpath_list, ':');
                 while (it.next()) |rpath| {
                     if (glibcVerFromRPath(rpath)) |ver| {
@@ -845,7 +764,7 @@ test glibcVerFromLinkName {
     try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
 }
 
-fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
+fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
     var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
         error.NameTooLong => unreachable,
         error.InvalidUtf8 => unreachable, // WASI only
@@ -879,7 +798,7 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
     // .dynstr section, and finding the max version number of symbols
     // that start with "GLIBC_2.".
     const glibc_so_basename = "libc.so.6";
-    var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
+    var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
         error.NameTooLong => unreachable,
         error.InvalidUtf8 => unreachable, // WASI only
         error.InvalidWtf8 => unreachable, // Windows only
@@ -913,16 +832,20 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
         error.Unexpected,
         => |e| return e,
     };
-    defer f.close();
+    defer file.close();
 
-    return glibcVerFromSoFile(f) catch |err| switch (err) {
+    // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
+    var buffer: [8000]u8 = undefined;
+    var file_reader: Io.File.Reader = .initAdapted(file, io, &buffer);
+
+    return glibcVerFromSoFile(&file_reader) catch |err| switch (err) {
         error.InvalidElfMagic,
         error.InvalidElfEndian,
         error.InvalidElfClass,
         error.InvalidElfFile,
         error.InvalidElfVersion,
         error.InvalidGnuLibCVersion,
-        error.UnexpectedEndOfFile,
+        error.EndOfStream,
         => return error.GLibCNotFound,
 
         error.SystemResources,
@@ -934,88 +857,34 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
     };
 }
 
-fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
-    var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
-    _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len);
-    const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf);
-    const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf);
-    if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
-    const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) {
-        elf.ELFDATA2LSB => .little,
-        elf.ELFDATA2MSB => .big,
-        else => return error.InvalidElfEndian,
-    };
-    const need_bswap = elf_endian != native_endian;
-    if (hdr32.e_ident[elf.EI.VERSION] != 1) return error.InvalidElfVersion;
-
-    const is_64 = switch (hdr32.e_ident[elf.EI.CLASS]) {
-        elf.ELFCLASS32 => false,
-        elf.ELFCLASS64 => true,
-        else => return error.InvalidElfClass,
+fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion {
+    const header = try elf.Header.read(&file_reader.interface);
+    const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
+    try file_reader.seekTo(str_section_off);
+    const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
+    var strtab_buf: [4096]u8 = undefined;
+    const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
+    try file_reader.seekTo(shstr.sh_offset);
+    try file_reader.interface.readSliceAll(shstrtab);
+    const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: {
+        var it = header.iterateSectionHeaders(&file_reader.interface);
+        while (it.next()) |shdr| {
+            const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
+            const sh_name = shstrtab[shdr.sh_name..end :0];
+            if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
+                .offset = shdr.sh_offset,
+                .size = shdr.sh_size,
+            };
+        } else return error.InvalidGnuLibCVersion;
     };
-    const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
-    var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
-    const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
-    const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
-    var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
-    if (sh_buf.len < shentsize) return error.InvalidElfFile;
-
-    _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize);
-    const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
-    const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
-    const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
-    const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
-    var strtab_buf: [4096:0]u8 = undefined;
-    const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
-    const shstrtab_read_len = try preadAtLeast(file, &strtab_buf, shstrtab_off, shstrtab_len);
-    const shstrtab = strtab_buf[0..shstrtab_read_len];
-    const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
-    var sh_i: u16 = 0;
-    const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
-        // Reserve some bytes so that we can deref the 64-bit struct fields
-        // even when the ELF file is 32-bits.
-        const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
-        const sh_read_byte_len = try preadAtLeast(
-            file,
-            sh_buf[0 .. sh_buf.len - sh_reserve],
-            shoff,
-            shentsize,
-        );
-        var sh_buf_i: usize = 0;
-        while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
-            sh_i += 1;
-            shoff += shentsize;
-            sh_buf_i += shentsize;
-        }) {
-            const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
-            const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
-            const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
-            const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
-            if (mem.eql(u8, sh_name, ".dynstr")) {
-                break :find_dyn_str .{
-                    .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
-                    .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
-                };
-            }
-        }
-    } else return error.InvalidGnuLibCVersion;
 
     // Here we loop over all the strings in the dynstr string table, assuming that any
     // strings that start with "GLIBC_2." indicate the existence of such a glibc version,
     // and furthermore, that the system-installed glibc is at minimum that version.
-
-    // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
-    // Here I use double this value plus some headroom. This makes it only need
-    // a single read syscall here.
-    var buf: [80000]u8 = undefined;
-    if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
-
-    const dynstr_size: usize = @intCast(dynstr.size);
-    const dynstr_bytes = buf[0..dynstr_size];
-    _ = try preadAtLeast(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
-    var it = mem.splitScalar(u8, dynstr_bytes, 0);
     var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
-    while (it.next()) |s| {
+
+    try file_reader.seekTo(dynstr.offset);
+    while (file_reader.interface.takeSentinel(0)) |s| {
         if (mem.startsWith(u8, s, "GLIBC_2.")) {
             const chopped = s["GLIBC_".len..];
             const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
@@ -1028,6 +897,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
             }
         }
     }
+
     return max_ver;
 }
 
@@ -1044,11 +914,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
 /// answer to these questions, or if there is a shebang line, then it chases the referenced
 /// file recursively. If that does not provide the answer, then the function falls back to
 /// defaults.
-fn detectAbiAndDynamicLinker(
-    cpu: Target.Cpu,
-    os: Target.Os,
-    query: Target.Query,
-) DetectError!Target {
+fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
     const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none;
     const is_linux = builtin.target.os.tag == .linux;
     const is_illumos = builtin.target.os.tag == .illumos;
@@ -1111,49 +977,52 @@ fn detectAbiAndDynamicLinker(
 
     const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
 
+    var file_reader: Io.File.Reader = undefined;
+    // According to `man 2 execve`:
+    //
+    // The kernel imposes a maximum length on the text
+    // that follows the "#!" characters at the start of a script;
+    // characters beyond the limit are ignored.
+    // Before Linux 5.1, the limit is 127 characters.
+    // Since Linux 5.1, the limit is 255 characters.
+    //
+    // Tests show that bash and zsh consider 255 as total limit,
+    // *including* "#!" characters and ignoring newline.
+    // For safety, we set max length as 255 + \n (1).
+    const max_shebang_line_size = 256;
+    var file_reader_buffer: [4096]u8 = undefined;
+    comptime assert(file_reader_buffer.len >= max_shebang_line_size);
+
     // Best case scenario: the executable is dynamically linked, and we can iterate
     // over our own shared objects and find a dynamic linker.
-    const elf_file = elf_file: {
-        // This block looks for a shebang line in /usr/bin/env,
-        // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
-        // doing the same logic recursively in case it finds another shebang line.
+    const header = elf_file: {
+        // This block looks for a shebang line in "/usr/bin/env". If it finds
+        // one, then instead of using "/usr/bin/env" as the ELF file to examine,
+        // it uses the file it references instead, doing the same logic
+        // recursively in case it finds another shebang line.
 
         var file_name: []const u8 = switch (os.tag) {
-            // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
-            // reasonably reliable path to start with.
+            // Since /usr/bin/env is hard-coded into the shebang line of many
+            // portable scripts, it's a reasonably reliable path to start with.
             else => "/usr/bin/env",
             // Haiku does not have a /usr root directory.
             .haiku => "/bin/env",
         };
 
-        // According to `man 2 execve`:
-        //
-        // The kernel imposes a maximum length on the text
-        // that follows the "#!" characters at the start of a script;
-        // characters beyond the limit are ignored.
-        // Before Linux 5.1, the limit is 127 characters.
-        // Since Linux 5.1, the limit is 255 characters.
-        //
-        // Tests show that bash and zsh consider 255 as total limit,
-        // *including* "#!" characters and ignoring newline.
-        // For safety, we set max length as 255 + \n (1).
-        var buffer: [255 + 1]u8 = undefined;
         while (true) {
-            // Interpreter path can be relative on Linux, but
-            // for simplicity we are asserting it is an absolute path.
             const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
-                error.NoSpaceLeft => unreachable,
-                error.NameTooLong => unreachable,
-                error.PathAlreadyExists => unreachable,
-                error.SharingViolation => unreachable,
-                error.InvalidUtf8 => unreachable, // WASI only
-                error.InvalidWtf8 => unreachable, // Windows only
-                error.BadPathName => unreachable,
-                error.PipeBusy => unreachable,
-                error.FileLocksNotSupported => unreachable,
-                error.WouldBlock => unreachable,
-                error.FileBusy => unreachable, // opened without write permissions
-                error.AntivirusInterference => unreachable, // Windows-only error
+                error.NoSpaceLeft => return error.Unexpected,
+                error.NameTooLong => return error.Unexpected,
+                error.PathAlreadyExists => return error.Unexpected,
+                error.SharingViolation => return error.Unexpected,
+                error.InvalidUtf8 => return error.Unexpected, // WASI only
+                error.InvalidWtf8 => return error.Unexpected, // Windows only
+                error.BadPathName => return error.Unexpected,
+                error.PipeBusy => return error.Unexpected,
+                error.FileLocksNotSupported => return error.Unexpected,
+                error.WouldBlock => return error.Unexpected,
+                error.FileBusy => return error.Unexpected, // opened without write permissions
+                error.AntivirusInterference => return error.Unexpected, // Windows-only error
 
                 error.IsDir,
                 error.NotDir,
@@ -1164,66 +1033,58 @@ fn detectAbiAndDynamicLinker(
                 error.NetworkNotFound,
                 error.FileTooBig,
                 error.Unexpected,
-                => |e| {
-                    std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
-                    return defaultAbiAndDynamicLinker(cpu, os, query);
-                },
+                => return error.UnableToOpenElfFile,
 
                 else => |e| return e,
             };
             var is_elf_file = false;
-            defer if (is_elf_file == false) file.close();
-
-            // Shortest working interpreter path is "#!/i" (4)
-            // (interpreter is "/i", assuming all paths are absolute, like in above comment).
-            // ELF magic number length is also 4.
-            //
-            // If file is shorter than that, it is definitely not ELF file
-            // nor file with "shebang" line.
-            const min_len: usize = 4;
-
-            const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) {
-                error.UnexpectedEndOfFile,
-                error.UnableToReadElfFile,
-                error.ProcessNotFound,
-                => return defaultAbiAndDynamicLinker(cpu, os, query),
+            defer if (!is_elf_file) file.close();
+
+            file_reader = .initAdapted(file, io, &file_reader_buffer);
+            file_name = undefined; // it aliases file_reader_buffer
+
+            const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) {
+                error.EndOfStream,
+                error.InvalidElfMagic,
+                => {
+                    const shebang_line = file_reader.interface.takeSentinel('\n') catch |err| switch (err) {
+                        error.ReadFailed => return file_reader.err.?,
+                        // It's neither an ELF file nor file with shebang line.
+                        error.EndOfStream, error.StreamTooLong => return error.UnhelpfulFile,
+                    };
+                    if (!mem.startsWith(u8, shebang_line, "#!")) return error.UnhelpfulFile;
+                    // We detected shebang, now parse entire line.
+
+                    // Trim leading "#!", spaces and tabs.
+                    const trimmed_line = mem.trimStart(u8, shebang_line[2..], &.{ ' ', '\t' });
+
+                    // This line can have:
+                    // * Interpreter path only,
+                    // * Interpreter path and arguments, all separated by space, tab or NUL character.
+                    // And optionally newline at the end.
+                    const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n");
+
+                    // Separate path and args.
+                    const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
+                    const unvalidated_path = path_maybe_args[0..path_end];
+                    file_name = if (fs.path.isAbsolute(unvalidated_path)) unvalidated_path else return error.RelativeShebang;
+                    continue;
+                },
 
-                else => |e| return e,
+                error.InvalidElfVersion,
+                error.InvalidElfClass,
+                error.InvalidElfEndian,
+                => return error.InvalidElfFile,
+
+                error.ReadFailed => return file_reader.err.?,
             };
-            const content = buffer[0..len];
-
-            if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
-                // It is very likely ELF file!
-                is_elf_file = true;
-                break :elf_file file;
-            } else if (mem.eql(u8, content[0..2], "#!")) {
-                // We detected shebang, now parse entire line.
-
-                // Trim leading "#!", spaces and tabs.
-                const trimmed_line = mem.trimStart(u8, content[2..], &.{ ' ', '\t' });
-
-                // This line can have:
-                // * Interpreter path only,
-                // * Interpreter path and arguments, all separated by space, tab or NUL character.
-                // And optionally newline at the end.
-                const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n");
-
-                // Separate path and args.
-                const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
-
-                file_name = path_maybe_args[0..path_end];
-                continue;
-            } else {
-                // Not a ELF file, not a shell script with "shebang line", invalid duck.
-                return defaultAbiAndDynamicLinker(cpu, os, query);
-            }
+            is_elf_file = true;
+            break :elf_file header;
         }
     };
-    defer elf_file.close();
+    defer file_reader.file.close(io);
 
-    // TODO: inline this function and combine the buffer we already read above to find
-    // the possible shebang line with the buffer we use for the ELF header.
-    return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {
+    return abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) {
         error.FileSystem,
         error.SystemResources,
         error.SymLinkLoop,
@@ -1232,6 +1093,8 @@ fn detectAbiAndDynamicLinker(
         error.ProcessNotFound,
         => |e| return e,
 
+        error.ReadFailed => return file_reader.err.?,
+
         error.UnableToReadElfFile,
         error.InvalidElfClass,
         error.InvalidElfVersion,
@@ -1239,12 +1102,12 @@ fn detectAbiAndDynamicLinker(
         error.InvalidElfFile,
         error.InvalidElfMagic,
         error.Unexpected,
-        error.UnexpectedEndOfFile,
+        error.EndOfStream,
         error.NameTooLong,
         error.StaticElfFile,
         // Finally, we fall back on the standard path.
         => |e| {
-            std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
+            std.log.warn("encountered {t}; falling back to default ABI and dynamic linker", .{e});
             return defaultAbiAndDynamicLinker(cpu, os, query);
         },
     };
@@ -1269,59 +1132,6 @@ const LdInfo = struct {
     abi: Target.Abi,
 };
 
-fn preadAtLeast(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
-    var i: usize = 0;
-    while (i < min_read_len) {
-        const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
-            error.OperationAborted => unreachable, // Windows-only
-            error.WouldBlock => unreachable, // Did not request blocking mode
-            error.Canceled => unreachable, // timerfd is unseekable
-            error.NotOpenForReading => unreachable,
-            error.SystemResources => return error.SystemResources,
-            error.IsDir => return error.UnableToReadElfFile,
-            error.BrokenPipe => return error.UnableToReadElfFile,
-            error.Unseekable => return error.UnableToReadElfFile,
-            error.ConnectionResetByPeer => return error.UnableToReadElfFile,
-            error.ConnectionTimedOut => return error.UnableToReadElfFile,
-            error.SocketUnconnected => return error.UnableToReadElfFile,
-            error.Unexpected => return error.Unexpected,
-            error.InputOutput => return error.FileSystem,
-            error.AccessDenied => return error.Unexpected,
-            error.ProcessNotFound => return error.ProcessNotFound,
-            error.LockViolation => return error.UnableToReadElfFile,
-        };
-        if (len == 0) return error.UnexpectedEndOfFile;
-        i += len;
-    }
-    return i;
-}
-
-fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
-    if (is_64) {
-        if (need_bswap) {
-            return @byteSwap(int_64);
-        } else {
-            return int_64;
-        }
-    } else {
-        if (need_bswap) {
-            return @byteSwap(int_32);
-        } else {
-            return int_32;
-        }
-    }
-}
-
-const builtin = @import("builtin");
-const std = @import("../std.zig");
-const mem = std.mem;
-const elf = std.elf;
-const fs = std.fs;
-const assert = std.debug.assert;
-const Target = std.Target;
-const native_endian = builtin.cpu.arch.endian();
-const posix = std.posix;
-
 test {
     _ = NativePaths;
 
lib/std/Build.zig
@@ -1,5 +1,7 @@
-const std = @import("std.zig");
 const builtin = @import("builtin");
+
+const std = @import("std.zig");
+const Io = std.Io;
 const fs = std.fs;
 const mem = std.mem;
 const debug = std.debug;
@@ -110,6 +112,7 @@ pub const ReleaseMode = enum {
 /// Shared state among all Build instances.
 /// Settings that are here rather than in Build are not configurable per-package.
 pub const Graph = struct {
+    io: Io,
     arena: Allocator,
     system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
     system_package_mode: bool = false,
@@ -2666,9 +2669,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
         // Hot path. This is faster than querying the native CPU and OS again.
         return b.graph.host;
     }
+    const io = b.graph.io;
     return .{
         .query = query,
-        .result = std.zig.system.resolveTargetQuery(query) catch
+        .result = std.zig.system.resolveTargetQuery(io, query) catch
             @panic("unable to resolve target query"),
     };
 }
lib/std/elf.zig
@@ -1,9 +1,11 @@
 //! Executable and Linkable Format.
 
 const std = @import("std.zig");
+const Io = std.Io;
 const math = std.math;
 const mem = std.mem;
 const assert = std.debug.assert;
+const Endian = std.builtin.Endian;
 const native_endian = @import("builtin").target.cpu.arch.endian();
 
 pub const AT_NULL = 0;
@@ -568,7 +570,7 @@ pub const ET = enum(u16) {
 /// All integers are native endian.
 pub const Header = struct {
     is_64: bool,
-    endian: std.builtin.Endian,
+    endian: Endian,
     os_abi: OSABI,
     /// The meaning of this value depends on `os_abi`.
     abi_version: u8,
@@ -583,48 +585,76 @@ pub const Header = struct {
     shnum: u16,
     shstrndx: u16,
 
-    pub fn iterateProgramHeaders(h: Header, file_reader: *std.fs.File.Reader) ProgramHeaderIterator {
+    pub fn iterateProgramHeaders(h: *const Header, file_reader: *Io.File.Reader) ProgramHeaderIterator {
         return .{
-            .elf_header = h,
+            .is_64 = h.is_64,
+            .endian = h.endian,
+            .phnum = h.phnum,
+            .phoff = h.phoff,
             .file_reader = file_reader,
         };
     }
 
-    pub fn iterateProgramHeadersBuffer(h: Header, buf: []const u8) ProgramHeaderBufferIterator {
+    pub fn iterateProgramHeadersBuffer(h: *const Header, buf: []const u8) ProgramHeaderBufferIterator {
         return .{
-            .elf_header = h,
+            .is_64 = h.is_64,
+            .endian = h.endian,
+            .phnum = h.phnum,
+            .phoff = h.phoff,
             .buf = buf,
         };
     }
 
-    pub fn iterateSectionHeaders(h: Header, file_reader: *std.fs.File.Reader) SectionHeaderIterator {
+    pub fn iterateSectionHeaders(h: *const Header, file_reader: *Io.File.Reader) SectionHeaderIterator {
         return .{
-            .elf_header = h,
+            .is_64 = h.is_64,
+            .endian = h.endian,
+            .shnum = h.shnum,
+            .shoff = h.shoff,
             .file_reader = file_reader,
         };
     }
 
-    pub fn iterateSectionHeadersBuffer(h: Header, buf: []const u8) SectionHeaderBufferIterator {
+    pub fn iterateSectionHeadersBuffer(h: *const Header, buf: []const u8) SectionHeaderBufferIterator {
         return .{
-            .elf_header = h,
+            .is_64 = h.is_64,
+            .endian = h.endian,
+            .shnum = h.shnum,
+            .shoff = h.shoff,
             .buf = buf,
         };
     }
 
-    pub const ReadError = std.Io.Reader.Error || error{
+    pub fn iterateDynamicSection(
+        h: *const Header,
+        file_reader: *Io.File.Reader,
+        offset: u64,
+        size: u64,
+    ) DynamicSectionIterator {
+        return .{
+            .is_64 = h.is_64,
+            .endian = h.endian,
+            .offset = offset,
+            .end_offset = offset + size,
+            .file_reader = file_reader,
+        };
+    }
+
+    pub const ReadError = Io.Reader.Error || error{
         InvalidElfMagic,
         InvalidElfVersion,
         InvalidElfClass,
         InvalidElfEndian,
     };
 
-    pub fn read(r: *std.Io.Reader) ReadError!Header {
+    /// If this function fails, seek position of `r` is unchanged.
+    pub fn read(r: *Io.Reader) ReadError!Header {
         const buf = try r.peek(@sizeOf(Elf64_Ehdr));
 
         if (!mem.eql(u8, buf[0..4], MAGIC)) return error.InvalidElfMagic;
         if (buf[EI.VERSION] != 1) return error.InvalidElfVersion;
 
-        const endian: std.builtin.Endian = switch (buf[EI.DATA]) {
+        const endian: Endian = switch (buf[EI.DATA]) {
             ELFDATA2LSB => .little,
             ELFDATA2MSB => .big,
             else => return error.InvalidElfEndian,
@@ -637,7 +667,7 @@ pub const Header = struct {
         };
     }
 
-    pub fn init(hdr: anytype, endian: std.builtin.Endian) Header {
+    pub fn init(hdr: anytype, endian: Endian) Header {
         // Converting integers to exhaustive enums using `@enumFromInt` could cause a panic.
         comptime assert(!@typeInfo(OSABI).@"enum".is_exhaustive);
         return .{
@@ -664,46 +694,54 @@ pub const Header = struct {
 };
 
 pub const ProgramHeaderIterator = struct {
-    elf_header: Header,
-    file_reader: *std.fs.File.Reader,
+    is_64: bool,
+    endian: Endian,
+    phnum: u16,
+    phoff: u64,
+
+    file_reader: *Io.File.Reader,
     index: usize = 0,
 
     pub fn next(it: *ProgramHeaderIterator) !?Elf64_Phdr {
-        if (it.index >= it.elf_header.phnum) return null;
+        if (it.index >= it.phnum) return null;
         defer it.index += 1;
 
-        const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
-        const offset = it.elf_header.phoff + size * it.index;
+        const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
+        const offset = it.phoff + size * it.index;
         try it.file_reader.seekTo(offset);
 
-        return takePhdr(&it.file_reader.interface, it.elf_header);
+        return takeProgramHeader(&it.file_reader.interface, it.is_64, it.endian);
     }
 };
 
 pub const ProgramHeaderBufferIterator = struct {
-    elf_header: Header,
+    is_64: bool,
+    endian: Endian,
+    phnum: u16,
+    phoff: u64,
+
     buf: []const u8,
     index: usize = 0,
 
     pub fn next(it: *ProgramHeaderBufferIterator) !?Elf64_Phdr {
-        if (it.index >= it.elf_header.phnum) return null;
+        if (it.index >= it.phnum) return null;
         defer it.index += 1;
 
-        const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
-        const offset = it.elf_header.phoff + size * it.index;
-        var reader = std.Io.Reader.fixed(it.buf[offset..]);
+        const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
+        const offset = it.phoff + size * it.index;
+        var reader = Io.Reader.fixed(it.buf[offset..]);
 
-        return takePhdr(&reader, it.elf_header);
+        return takeProgramHeader(&reader, it.is_64, it.endian);
     }
 };
 
-fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {
-    if (elf_header.is_64) {
-        const phdr = try reader.takeStruct(Elf64_Phdr, elf_header.endian);
+pub fn takeProgramHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Phdr {
+    if (is_64) {
+        const phdr = try reader.takeStruct(Elf64_Phdr, endian);
         return phdr;
     }
 
-    const phdr = try reader.takeStruct(Elf32_Phdr, elf_header.endian);
+    const phdr = try reader.takeStruct(Elf32_Phdr, endian);
     return .{
         .p_type = phdr.p_type,
         .p_offset = phdr.p_offset,
@@ -717,47 +755,55 @@ fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {
 }
 
 pub const SectionHeaderIterator = struct {
-    elf_header: Header,
-    file_reader: *std.fs.File.Reader,
+    is_64: bool,
+    endian: Endian,
+    shnum: u16,
+    shoff: u64,
+
+    file_reader: *Io.File.Reader,
     index: usize = 0,
 
     pub fn next(it: *SectionHeaderIterator) !?Elf64_Shdr {
-        if (it.index >= it.elf_header.shnum) return null;
+        if (it.index >= it.shnum) return null;
         defer it.index += 1;
 
-        const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
-        const offset = it.elf_header.shoff + size * it.index;
+        const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
+        const offset = it.shoff + size * it.index;
         try it.file_reader.seekTo(offset);
 
-        return takeShdr(&it.file_reader.interface, it.elf_header);
+        return takeSectionHeader(&it.file_reader.interface, it.is_64, it.endian);
     }
 };
 
 pub const SectionHeaderBufferIterator = struct {
-    elf_header: Header,
+    is_64: bool,
+    endian: Endian,
+    shnum: u16,
+    shoff: u64,
+
     buf: []const u8,
     index: usize = 0,
 
     pub fn next(it: *SectionHeaderBufferIterator) !?Elf64_Shdr {
-        if (it.index >= it.elf_header.shnum) return null;
+        if (it.index >= it.shnum) return null;
         defer it.index += 1;
 
-        const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
-        const offset = it.elf_header.shoff + size * it.index;
+        const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
+        const offset = it.shoff + size * it.index;
         if (offset > it.buf.len) return error.EndOfStream;
-        var reader = std.Io.Reader.fixed(it.buf[@intCast(offset)..]);
+        var reader = Io.Reader.fixed(it.buf[@intCast(offset)..]);
 
-        return takeShdr(&reader, it.elf_header);
+        return takeSectionHeader(&reader, it.is_64, it.endian);
     }
 };
 
-fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {
-    if (elf_header.is_64) {
-        const shdr = try reader.takeStruct(Elf64_Shdr, elf_header.endian);
+pub fn takeSectionHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Shdr {
+    if (is_64) {
+        const shdr = try reader.takeStruct(Elf64_Shdr, endian);
         return shdr;
     }
 
-    const shdr = try reader.takeStruct(Elf32_Shdr, elf_header.endian);
+    const shdr = try reader.takeStruct(Elf32_Shdr, endian);
     return .{
         .sh_name = shdr.sh_name,
         .sh_type = shdr.sh_type,
@@ -772,6 +818,36 @@ fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {
     };
 }
 
+pub const DynamicSectionIterator = struct {
+    is_64: bool,
+    endian: Endian,
+    offset: u64,
+    end_offset: u64,
+
+    file_reader: *Io.File.Reader,
+
+    pub fn next(it: *SectionHeaderIterator) !?Elf64_Dyn {
+        if (it.offset >= it.end_offset) return null;
+        const size: u64 = if (it.is_64) @sizeOf(Elf64_Dyn) else @sizeOf(Elf32_Dyn);
+        defer it.offset += size;
+        try it.file_reader.seekTo(it.offset);
+        return takeDynamicSection(&it.file_reader.interface, it.is_64, it.endian);
+    }
+};
+
+pub fn takeDynamicSection(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Dyn {
+    if (is_64) {
+        const dyn = try reader.takeStruct(Elf64_Dyn, endian);
+        return dyn;
+    }
+
+    const dyn = try reader.takeStruct(Elf32_Dyn, endian);
+    return .{
+        .d_tag = dyn.d_tag,
+        .d_val = dyn.d_val,
+    };
+}
+
 pub const EI = struct {
     pub const CLASS = 4;
     pub const DATA = 5;
lib/std/Io.zig
@@ -738,9 +738,9 @@ pub const Timestamp = struct {
         /// * On Linux, corresponds `CLOCK_MONOTONIC`.
         /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`.
         awake,
-        /// Identical to `awake` except it expresses intent to include time
-        /// that the system is suspended, however, it may be implemented
-        /// identically to `awake`.
+        /// Identical to `awake` except it expresses intent to **include time
+        /// that the system is suspended**, however, due to limitations it may
+        /// behave identically to `awake`.
         ///
         /// * On Linux, corresponds `CLOCK_BOOTTIME`.
         /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`.
lib/std/zig.zig
@@ -6,6 +6,7 @@ const std = @import("std.zig");
 const tokenizer = @import("zig/tokenizer.zig");
 const assert = std.debug.assert;
 const Allocator = std.mem.Allocator;
+const Io = std.Io;
 const Writer = std.Io.Writer;
 
 pub const ErrorBundle = @import("zig/ErrorBundle.zig");
@@ -52,9 +53,9 @@ pub const Color = enum {
     /// Assume stderr is a terminal.
     on,
 
-    pub fn get_tty_conf(color: Color) std.Io.tty.Config {
+    pub fn get_tty_conf(color: Color) Io.tty.Config {
         return switch (color) {
-            .auto => std.Io.tty.detectConfig(std.fs.File.stderr()),
+            .auto => Io.tty.detectConfig(std.fs.File.stderr()),
             .on => .escape_codes,
             .off => .no_color,
         };
@@ -323,7 +324,7 @@ pub const BuildId = union(enum) {
         try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));
     }
 
-    pub fn format(id: BuildId, writer: *std.Io.Writer) std.Io.Writer.Error!void {
+    pub fn format(id: BuildId, writer: *Writer) Writer.Error!void {
         switch (id) {
             .none, .fast, .uuid, .sha1, .md5 => {
                 try writer.writeAll(@tagName(id));
@@ -620,8 +621,8 @@ pub fn putAstErrorsIntoBundle(
     try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);
 }
 
-pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target {
-    return std.zig.system.resolveTargetQuery(target_query) catch |err|
+pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target {
+    return std.zig.system.resolveTargetQuery(io, target_query) catch |err|
         std.process.fatal("unable to resolve target: {s}", .{@errorName(err)});
 }
 
src/main.zig
@@ -1,5 +1,8 @@
-const std = @import("std");
 const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
+const std = @import("std");
+const Io = std.Io;
 const assert = std.debug.assert;
 const fs = std.fs;
 const mem = std.mem;
@@ -10,7 +13,6 @@ const Color = std.zig.Color;
 const warn = std.log.warn;
 const ThreadPool = std.Thread.Pool;
 const cleanExit = std.process.cleanExit;
-const native_os = builtin.os.tag;
 const Cache = std.Build.Cache;
 const Path = std.Build.Cache.Path;
 const Directory = std.Build.Cache.Directory;
@@ -245,26 +247,30 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         }
     }
 
+    var threaded: Io.Threaded = .init(gpa);
+    defer threaded.deinit();
+    const io = threaded.io();
+
     const cmd = args[1];
     const cmd_args = args[2..];
     if (mem.eql(u8, cmd, "build-exe")) {
         dev.check(.build_exe_command);
-        return buildOutputType(gpa, arena, args, .{ .build = .Exe });
+        return buildOutputType(gpa, arena, io, args, .{ .build = .Exe });
     } else if (mem.eql(u8, cmd, "build-lib")) {
         dev.check(.build_lib_command);
-        return buildOutputType(gpa, arena, args, .{ .build = .Lib });
+        return buildOutputType(gpa, arena, io, args, .{ .build = .Lib });
     } else if (mem.eql(u8, cmd, "build-obj")) {
         dev.check(.build_obj_command);
-        return buildOutputType(gpa, arena, args, .{ .build = .Obj });
+        return buildOutputType(gpa, arena, io, args, .{ .build = .Obj });
     } else if (mem.eql(u8, cmd, "test")) {
         dev.check(.test_command);
-        return buildOutputType(gpa, arena, args, .zig_test);
+        return buildOutputType(gpa, arena, io, args, .zig_test);
     } else if (mem.eql(u8, cmd, "test-obj")) {
         dev.check(.test_command);
-        return buildOutputType(gpa, arena, args, .zig_test_obj);
+        return buildOutputType(gpa, arena, io, args, .zig_test_obj);
     } else if (mem.eql(u8, cmd, "run")) {
         dev.check(.run_command);
-        return buildOutputType(gpa, arena, args, .run);
+        return buildOutputType(gpa, arena, io, args, .run);
     } else if (mem.eql(u8, cmd, "dlltool") or
         mem.eql(u8, cmd, "ranlib") or
         mem.eql(u8, cmd, "lib") or
@@ -274,7 +280,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         return process.exit(try llvmArMain(arena, args));
     } else if (mem.eql(u8, cmd, "build")) {
         dev.check(.build_command);
-        return cmdBuild(gpa, arena, cmd_args);
+        return cmdBuild(gpa, arena, io, cmd_args);
     } else if (mem.eql(u8, cmd, "clang") or
         mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))
     {
@@ -288,16 +294,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         return process.exit(try lldMain(arena, args, true));
     } else if (mem.eql(u8, cmd, "cc")) {
         dev.check(.cc_command);
-        return buildOutputType(gpa, arena, args, .cc);
+        return buildOutputType(gpa, arena, io, args, .cc);
     } else if (mem.eql(u8, cmd, "c++")) {
         dev.check(.cc_command);
-        return buildOutputType(gpa, arena, args, .cpp);
+        return buildOutputType(gpa, arena, io, args, .cpp);
     } else if (mem.eql(u8, cmd, "translate-c")) {
         dev.check(.translate_c_command);
-        return buildOutputType(gpa, arena, args, .translate_c);
+        return buildOutputType(gpa, arena, io, args, .translate_c);
     } else if (mem.eql(u8, cmd, "rc")) {
         const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration");
-        return jitCmd(gpa, arena, cmd_args, .{
+        return jitCmd(gpa, arena, io, cmd_args, .{
             .cmd_name = "resinator",
             .root_src_path = "resinator/main.zig",
             .depend_on_aro = true,
@@ -308,20 +314,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         dev.check(.fmt_command);
         return @import("fmt.zig").run(gpa, arena, cmd_args);
     } else if (mem.eql(u8, cmd, "objcopy")) {
-        return jitCmd(gpa, arena, cmd_args, .{
+        return jitCmd(gpa, arena, io, cmd_args, .{
             .cmd_name = "objcopy",
             .root_src_path = "objcopy.zig",
         });
     } else if (mem.eql(u8, cmd, "fetch")) {
         return cmdFetch(gpa, arena, cmd_args);
     } else if (mem.eql(u8, cmd, "libc")) {
-        return jitCmd(gpa, arena, cmd_args, .{
+        return jitCmd(gpa, arena, io, cmd_args, .{
             .cmd_name = "libc",
             .root_src_path = "libc.zig",
             .prepend_zig_lib_dir_path = true,
         });
     } else if (mem.eql(u8, cmd, "std")) {
-        return jitCmd(gpa, arena, cmd_args, .{
+        return jitCmd(gpa, arena, io, cmd_args, .{
             .cmd_name = "std",
             .root_src_path = "std-docs.zig",
             .prepend_zig_lib_dir_path = true,
@@ -332,7 +338,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         return cmdInit(gpa, arena, cmd_args);
     } else if (mem.eql(u8, cmd, "targets")) {
         dev.check(.targets_command);
-        const host = std.zig.resolveTargetQueryOrFatal(.{});
+        const host = std.zig.resolveTargetQueryOrFatal(io, .{});
         var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
         try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);
         return stdout_writer.interface.flush();
@@ -351,7 +357,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         );
         return stdout_writer.interface.flush();
     } else if (mem.eql(u8, cmd, "reduce")) {
-        return jitCmd(gpa, arena, cmd_args, .{
+        return jitCmd(gpa, arena, io, cmd_args, .{
             .cmd_name = "reduce",
             .root_src_path = "reduce.zig",
         });
@@ -364,7 +370,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
     } else if (mem.eql(u8, cmd, "ast-check")) {
         return cmdAstCheck(arena, cmd_args);
     } else if (mem.eql(u8, cmd, "detect-cpu")) {
-        return cmdDetectCpu(cmd_args);
+        return cmdDetectCpu(io, cmd_args);
     } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "changelist")) {
         return cmdChangelist(arena, cmd_args);
     } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {
@@ -792,6 +798,7 @@ const CliModule = struct {
 fn buildOutputType(
     gpa: Allocator,
     arena: Allocator,
+    io: Io,
     all_args: []const []const u8,
     arg_mode: ArgMode,
 ) !void {
@@ -3017,7 +3024,7 @@ fn buildOutputType(
     create_module.opts.emit_bin = emit_bin != .no;
     create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0;
 
-    const main_mod = try createModule(gpa, arena, &create_module, 0, null, color);
+    const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color);
     for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| {
         if (cli_mod.resolved == null)
             fatal("module '{s}' declared but not used", .{key});
@@ -3545,6 +3552,7 @@ fn buildOutputType(
             var stdin_reader = fs.File.stdin().reader(&stdin_buffer);
             var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
             try serve(
+                io,
                 comp,
                 &stdin_reader.interface,
                 &stdout_writer.interface,
@@ -3571,6 +3579,7 @@ fn buildOutputType(
             var output = conn.stream.writer(&stdout_buffer);
 
             try serve(
+                io,
                 comp,
                 input.interface(),
                 &output.interface,
@@ -3646,6 +3655,7 @@ fn buildOutputType(
             comp,
             gpa,
             arena,
+            io,
             test_exec_args.items,
             self_exe_path,
             arg_mode,
@@ -3704,6 +3714,7 @@ const CreateModule = struct {
 fn createModule(
     gpa: Allocator,
     arena: Allocator,
+    io: Io,
     create_module: *CreateModule,
     index: usize,
     parent: ?*Package.Module,
@@ -3777,7 +3788,7 @@ fn createModule(
         }
 
         const target_query = std.zig.parseTargetQueryOrReportFatalError(arena, target_parse_options);
-        const target = std.zig.resolveTargetQueryOrFatal(target_query);
+        const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
         break :t .{
             .result = target,
             .is_native_os = target_query.isNativeOs(),
@@ -4022,7 +4033,7 @@ fn createModule(
     for (cli_mod.deps) |dep| {
         const dep_index = create_module.modules.getIndex(dep.value) orelse
             fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key });
-        const dep_mod = try createModule(gpa, arena, create_module, dep_index, mod, color);
+        const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color);
         try mod.deps.put(arena, dep.key, dep_mod);
     }
 
@@ -4038,9 +4049,10 @@ fn saveState(comp: *Compilation, incremental: bool) void {
 }
 
 fn serve(
+    io: Io,
     comp: *Compilation,
-    in: *std.Io.Reader,
-    out: *std.Io.Writer,
+    in: *Io.Reader,
+    out: *Io.Writer,
     test_exec_args: []const ?[]const u8,
     self_exe_path: ?[]const u8,
     arg_mode: ArgMode,
@@ -4090,7 +4102,7 @@ fn serve(
                     defer arena_instance.deinit();
                     const arena = arena_instance.allocator();
                     var output: Compilation.CImportResult = undefined;
-                    try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node);
+                    try cmdTranslateC(io, comp, arena, &output, file_system_inputs, main_progress_node);
                     defer output.deinit(gpa);
 
                     if (file_system_inputs.items.len != 0) {
@@ -4126,6 +4138,7 @@ fn serve(
                 //    comp,
                 //    gpa,
                 //    arena,
+                //    io,
                 //    test_exec_args,
                 //    self_exe_path.?,
                 //    arg_mode,
@@ -4280,6 +4293,7 @@ fn runOrTest(
     comp: *Compilation,
     gpa: Allocator,
     arena: Allocator,
+    io: Io,
     test_exec_args: []const ?[]const u8,
     self_exe_path: []const u8,
     arg_mode: ArgMode,
@@ -4334,7 +4348,7 @@ fn runOrTest(
         std.debug.lockStdErr();
         const err = process.execve(gpa, argv.items, &env_map);
         std.debug.unlockStdErr();
-        try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
+        try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
         const cmd = try std.mem.join(arena, " ", argv.items);
         fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
     } else if (process.can_spawn) {
@@ -4355,7 +4369,7 @@ fn runOrTest(
             break :t child.spawnAndWait();
         };
         const term = term_result catch |err| {
-            try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
+            try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
             const cmd = try std.mem.join(arena, " ", argv.items);
             fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
         };
@@ -4594,11 +4608,12 @@ fn cmdTranslateC(
 pub fn translateC(
     gpa: Allocator,
     arena: Allocator,
+    io: Io,
     argv: []const []const u8,
     prog_node: std.Progress.Node,
     capture: ?*[]u8,
 ) !void {
-    try jitCmd(gpa, arena, argv, .{
+    try jitCmd(gpa, arena, io, argv, .{
         .cmd_name = "translate-c",
         .root_src_path = "translate-c/main.zig",
         .depend_on_aro = true,
@@ -4755,7 +4770,7 @@ test sanitizeExampleName {
     try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));
 }
 
-fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
+fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {
     dev.check(.build_command);
 
     var build_file: ?[]const u8 = null;
@@ -4983,7 +4998,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
                     .arch_os_abi = triple,
                 });
                 break :t .{
-                    .result = std.zig.resolveTargetQueryOrFatal(target_query),
+                    .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
                     .is_native_os = false,
                     .is_native_abi = false,
                     .is_explicit_dynamic_linker = false,
@@ -4991,7 +5006,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
             }
         }
         break :t .{
-            .result = std.zig.resolveTargetQueryOrFatal(.{}),
+            .result = std.zig.resolveTargetQueryOrFatal(io, .{}),
             .is_native_os = true,
             .is_native_abi = true,
             .is_explicit_dynamic_linker = false,
@@ -5400,6 +5415,7 @@ const JitCmdOptions = struct {
 fn jitCmd(
     gpa: Allocator,
     arena: Allocator,
+    io: Io,
     args: []const []const u8,
     options: JitCmdOptions,
 ) !void {
@@ -5412,7 +5428,7 @@ fn jitCmd(
 
     const target_query: std.Target.Query = .{};
     const resolved_target: Package.Module.ResolvedTarget = .{
-        .result = std.zig.resolveTargetQueryOrFatal(target_query),
+        .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
         .is_native_os = true,
         .is_native_abi = true,
         .is_explicit_dynamic_linker = false,
@@ -6209,7 +6225,7 @@ fn cmdAstCheck(
     }
 }
 
-fn cmdDetectCpu(args: []const []const u8) !void {
+fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
     dev.check(.detect_cpu_command);
 
     const detect_cpu_usage =
@@ -6254,7 +6270,7 @@ fn cmdDetectCpu(args: []const []const u8) !void {
         const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);
         try printCpu(cpu);
     } else {
-        const host_target = std.zig.resolveTargetQueryOrFatal(.{});
+        const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
         try printCpu(host_target.cpu);
     }
 }
@@ -6521,13 +6537,14 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {
 }
 
 fn warnAboutForeignBinaries(
+    io: Io,
     arena: Allocator,
     arg_mode: ArgMode,
     target: *const std.Target,
     link_libc: bool,
 ) !void {
     const host_query: std.Target.Query = .{};
-    const host_target = std.zig.resolveTargetQueryOrFatal(host_query);
+    const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
 
     switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) {
         .native => return,
@@ -7080,7 +7097,7 @@ fn cmdFetch(
         try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
     }
 
-    var aw: std.Io.Writer.Allocating = .init(gpa);
+    var aw: Io.Writer.Allocating = .init(gpa);
     defer aw.deinit();
     try ast.render(gpa, &aw.writer, fixups);
     const rendered = aw.written();