Commit 6713745ec4

Andrew Kelley <andrew@ziglang.org>
2025-08-17 05:01:30
link.Elf: update to not use GenericWriter
1 parent cc93166
src/link/Elf/Archive.zig
@@ -123,8 +123,7 @@ pub fn setArHdr(opts: struct {
     @memcpy(&hdr.ar_fmag, elf.ARFMAG);
 
     {
-        var stream = std.io.fixedBufferStream(&hdr.ar_name);
-        const writer = stream.writer();
+        var writer: std.Io.Writer = .fixed(&hdr.ar_name);
         switch (opts.name) {
             .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable,
             .strtab => writer.print("//", .{}) catch unreachable,
@@ -133,8 +132,8 @@ pub fn setArHdr(opts: struct {
         }
     }
     {
-        var stream = std.io.fixedBufferStream(&hdr.ar_size);
-        stream.writer().print("{d}", .{opts.size}) catch unreachable;
+        var writer: std.Io.Writer = .fixed(&hdr.ar_size);
+        writer.print("{d}", .{opts.size}) catch unreachable;
     }
 
     return hdr;
@@ -246,7 +245,7 @@ pub const ArStrtab = struct {
 
     pub fn insert(ar: *ArStrtab, allocator: Allocator, name: []const u8) error{OutOfMemory}!u32 {
         const off = @as(u32, @intCast(ar.buffer.items.len));
-        try ar.buffer.writer(allocator).print("{s}/{c}", .{ name, strtab_delimiter });
+        try ar.buffer.print(allocator, "{s}/{c}", .{ name, strtab_delimiter });
         return off;
     }
 
src/link/Elf/Atom.zig
@@ -621,7 +621,6 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi
 
     const cpu_arch = elf_file.getTarget().cpu.arch;
     const file_ptr = self.file(elf_file).?;
-    var stream = std.io.fixedBufferStream(code);
 
     const rels = self.relocs(elf_file);
     var it = RelocsIterator{ .relocs = rels };
@@ -661,20 +660,16 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi
             target.name(elf_file),
         });
 
-        try stream.seekTo(r_offset);
-
         const args = ResolveArgs{ P, A, S, GOT, G, TP, DTP };
 
         switch (cpu_arch) {
-            .x86_64 => x86_64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) {
+            .x86_64 => x86_64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code) catch |err| switch (err) {
                 error.RelocFailure,
                 error.RelaxFailure,
-                error.InvalidInstruction,
-                error.CannotEncode,
                 => has_reloc_errors = true,
                 else => |e| return e,
             },
-            .aarch64, .aarch64_be => aarch64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) {
+            .aarch64, .aarch64_be => aarch64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code) catch |err| switch (err) {
                 error.RelocFailure,
                 error.RelaxFailure,
                 error.UnexpectedRemainder,
@@ -682,7 +677,7 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!voi
                 => has_reloc_errors = true,
                 else => |e| return e,
             },
-            .riscv64, .riscv64be => riscv.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) {
+            .riscv64, .riscv64be => riscv.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code) catch |err| switch (err) {
                 error.RelocFailure,
                 error.RelaxFailure,
                 => has_reloc_errors = true,
@@ -701,7 +696,8 @@ fn resolveDynAbsReloc(
     rel: elf.Elf64_Rela,
     action: RelocAction,
     elf_file: *Elf,
-    writer: anytype,
+    code: []u8,
+    r_offset: usize,
 ) !void {
     const comp = elf_file.base.comp;
     const gpa = comp.gpa;
@@ -726,7 +722,7 @@ fn resolveDynAbsReloc(
         .copyrel,
         .cplt,
         .none,
-        => try writer.writeInt(i64, S + A, .little),
+        => mem.writeInt(i64, code[r_offset..][0..8], S + A, .little),
 
         .dyn_copyrel => {
             if (is_writeable or elf_file.z_nocopyreloc) {
@@ -737,9 +733,9 @@ fn resolveDynAbsReloc(
                     .addend = A,
                     .target = target,
                 });
-                try applyDynamicReloc(A, elf_file, writer);
+                applyDynamicReloc(A, code, r_offset);
             } else {
-                try writer.writeInt(i64, S + A, .little);
+                mem.writeInt(i64, code[r_offset..][0..8], S + A, .little);
             }
         },
 
@@ -752,9 +748,9 @@ fn resolveDynAbsReloc(
                     .addend = A,
                     .target = target,
                 });
-                try applyDynamicReloc(A, elf_file, writer);
+                applyDynamicReloc(A, code, r_offset);
             } else {
-                try writer.writeInt(i64, S + A, .little);
+                mem.writeInt(i64, code[r_offset..][0..8], S + A, .little);
             }
         },
 
@@ -766,7 +762,7 @@ fn resolveDynAbsReloc(
                 .addend = A,
                 .target = target,
             });
-            try applyDynamicReloc(A, elf_file, writer);
+            applyDynamicReloc(A, code, r_offset);
         },
 
         .baserel => {
@@ -776,7 +772,7 @@ fn resolveDynAbsReloc(
                 .addend = S + A,
                 .target = target,
             });
-            try applyDynamicReloc(S + A, elf_file, writer);
+            applyDynamicReloc(S + A, code, r_offset);
         },
 
         .ifunc => {
@@ -787,16 +783,13 @@ fn resolveDynAbsReloc(
                 .addend = S_ + A,
                 .target = target,
             });
-            try applyDynamicReloc(S_ + A, elf_file, writer);
+            applyDynamicReloc(S_ + A, code, r_offset);
         },
     }
 }
 
-fn applyDynamicReloc(value: i64, elf_file: *Elf, writer: anytype) !void {
-    _ = elf_file;
-    // if (elf_file.options.apply_dynamic_relocs) {
-    try writer.writeInt(i64, value, .little);
-    // }
+fn applyDynamicReloc(value: i64, code: []u8, r_offset: usize) void {
+    mem.writeInt(i64, code[r_offset..][0..8], value, .little);
 }
 
 pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: anytype) !void {
@@ -804,7 +797,6 @@ pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: any
 
     const cpu_arch = elf_file.getTarget().cpu.arch;
     const file_ptr = self.file(elf_file).?;
-    var stream = std.io.fixedBufferStream(code);
 
     const rels = self.relocs(elf_file);
     var has_reloc_errors = false;
@@ -863,18 +855,16 @@ pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: any
             target.name(elf_file),
         });
 
-        try stream.seekTo(r_offset);
-
         switch (cpu_arch) {
-            .x86_64 => x86_64.resolveRelocNonAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) {
+            .x86_64 => x86_64.resolveRelocNonAlloc(self, elf_file, rel, target, args, code[r_offset..]) catch |err| switch (err) {
                 error.RelocFailure => has_reloc_errors = true,
                 else => |e| return e,
             },
-            .aarch64, .aarch64_be => aarch64.resolveRelocNonAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) {
+            .aarch64, .aarch64_be => aarch64.resolveRelocNonAlloc(self, elf_file, rel, target, args, code[r_offset..]) catch |err| switch (err) {
                 error.RelocFailure => has_reloc_errors = true,
                 else => |e| return e,
             },
-            .riscv64, .riscv64be => riscv.resolveRelocNonAlloc(self, elf_file, rel, target, args, &it, code, &stream) catch |err| switch (err) {
+            .riscv64, .riscv64be => riscv.resolveRelocNonAlloc(self, elf_file, rel, target, args, code[r_offset..]) catch |err| switch (err) {
                 error.RelocFailure => has_reloc_errors = true,
                 else => |e| return e,
             },
@@ -915,7 +905,7 @@ const Format = struct {
     atom: Atom,
     elf_file: *Elf,
 
-    fn default(f: Format, w: *std.io.Writer) std.io.Writer.Error!void {
+    fn default(f: Format, w: *Writer) Writer.Error!void {
         const atom = f.atom;
         const elf_file = f.elf_file;
         try w.print("atom({d}) : {s} : @{x} : shdr({d}) : align({x}) : size({x}) : prev({f}) : next({f})", .{
@@ -1068,16 +1058,13 @@ const x86_64 = struct {
         args: ResolveArgs,
         it: *RelocsIterator,
         code: []u8,
-        stream: anytype,
-    ) (error{ InvalidInstruction, CannotEncode } || RelocError)!void {
+    ) !void {
         dev.check(.x86_64_backend);
         const t = &elf_file.base.comp.root_mod.resolved_target.result;
         const diags = &elf_file.base.comp.link_diags;
         const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type());
         const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
 
-        const cwriter = stream.writer();
-
         const P, const A, const S, const GOT, const G, const TP, const DTP = args;
 
         switch (r_type) {
@@ -1089,58 +1076,60 @@ const x86_64 = struct {
                     rel,
                     dynAbsRelocAction(target, elf_file),
                     elf_file,
-                    cwriter,
+                    code,
+                    r_offset,
                 );
             },
 
-            .PLT32 => try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little),
-            .PC32 => try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little),
+            .PLT32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little),
+            .PC32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little),
 
-            .GOTPCREL => try cwriter.writeInt(i32, @as(i32, @intCast(G + GOT + A - P)), .little),
-            .GOTPC32 => try cwriter.writeInt(i32, @as(i32, @intCast(GOT + A - P)), .little),
-            .GOTPC64 => try cwriter.writeInt(i64, GOT + A - P, .little),
+            .GOTPCREL => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + GOT + A - P)), .little),
+            .GOTPC32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(GOT + A - P)), .little),
+            .GOTPC64 => mem.writeInt(i64, code[r_offset..][0..8], GOT + A - P, .little),
 
             .GOTPCRELX => {
                 if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: {
                     x86_64.relaxGotpcrelx(code[r_offset - 2 ..], t) catch break :blk;
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little);
                     return;
                 }
-                try cwriter.writeInt(i32, @as(i32, @intCast(G + GOT + A - P)), .little);
+                mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + GOT + A - P)), .little);
             },
 
             .REX_GOTPCRELX => {
                 if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: {
                     x86_64.relaxRexGotpcrelx(code[r_offset - 3 ..], t) catch break :blk;
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S + A - P)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little);
                     return;
                 }
-                try cwriter.writeInt(i32, @as(i32, @intCast(G + GOT + A - P)), .little);
+                mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + GOT + A - P)), .little);
             },
 
-            .@"32" => try cwriter.writeInt(u32, @as(u32, @truncate(@as(u64, @intCast(S + A)))), .little),
-            .@"32S" => try cwriter.writeInt(i32, @as(i32, @truncate(S + A)), .little),
+            .@"32" => mem.writeInt(u32, code[r_offset..][0..4], @as(u32, @truncate(@as(u64, @intCast(S + A)))), .little),
+            .@"32S" => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A)), .little),
 
-            .TPOFF32 => try cwriter.writeInt(i32, @as(i32, @truncate(S + A - TP)), .little),
-            .TPOFF64 => try cwriter.writeInt(i64, S + A - TP, .little),
+            .TPOFF32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A - TP)), .little),
+            .TPOFF64 => mem.writeInt(i64, code[r_offset..][0..8], S + A - TP, .little),
 
-            .DTPOFF32 => try cwriter.writeInt(i32, @as(i32, @truncate(S + A - DTP)), .little),
-            .DTPOFF64 => try cwriter.writeInt(i64, S + A - DTP, .little),
+            .DTPOFF32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A - DTP)), .little),
+            .DTPOFF64 => mem.writeInt(i64, code[r_offset..][0..8], S + A - DTP, .little),
 
             .TLSGD => {
                 if (target.flags.has_tlsgd) {
                     const S_ = target.tlsGdAddress(elf_file);
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S_ + A - P)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
                 } else if (target.flags.has_gottp) {
                     const S_ = target.gotTpAddress(elf_file);
-                    try x86_64.relaxTlsGdToIe(atom, &.{ rel, it.next().? }, @intCast(S_ - P), elf_file, stream);
+                    try x86_64.relaxTlsGdToIe(atom, &.{ rel, it.next().? }, @intCast(S_ - P), elf_file, code, r_offset);
                 } else {
                     try x86_64.relaxTlsGdToLe(
                         atom,
                         &.{ rel, it.next().? },
                         @as(i32, @intCast(S - TP)),
                         elf_file,
-                        stream,
+                        code,
+                        r_offset,
                     );
                 }
             },
@@ -1149,14 +1138,15 @@ const x86_64 = struct {
                 if (elf_file.got.tlsld_index) |entry_index| {
                     const tlsld_entry = elf_file.got.entries.items[entry_index];
                     const S_ = tlsld_entry.address(elf_file);
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S_ + A - P)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
                 } else {
                     try x86_64.relaxTlsLdToLe(
                         atom,
                         &.{ rel, it.next().? },
                         @as(i32, @intCast(TP - elf_file.tlsAddress())),
                         elf_file,
-                        stream,
+                        code,
+                        r_offset,
                     );
                 }
             },
@@ -1164,7 +1154,7 @@ const x86_64 = struct {
             .GOTPC32_TLSDESC => {
                 if (target.flags.has_tlsdesc) {
                     const S_ = target.tlsDescAddress(elf_file);
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S_ + A - P)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
                 } else {
                     x86_64.relaxGotPcTlsDesc(code[r_offset - 3 ..], t) catch {
                         var err = try diags.addErrorWithNotes(1);
@@ -1176,26 +1166,26 @@ const x86_64 = struct {
                         });
                         return error.RelaxFailure;
                     };
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S - TP)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S - TP)), .little);
                 }
             },
 
             .TLSDESC_CALL => if (!target.flags.has_tlsdesc) {
                 // call -> nop
-                try cwriter.writeAll(&.{ 0x66, 0x90 });
+                code[r_offset..][0..2].* = .{ 0x66, 0x90 };
             },
 
             .GOTTPOFF => {
                 if (target.flags.has_gottp) {
                     const S_ = target.gotTpAddress(elf_file);
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S_ + A - P)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
                 } else {
                     x86_64.relaxGotTpOff(code[r_offset - 3 ..], t);
-                    try cwriter.writeInt(i32, @as(i32, @intCast(S - TP)), .little);
+                    mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S - TP)), .little);
                 }
             },
 
-            .GOT32 => try cwriter.writeInt(i32, @as(i32, @intCast(G + A)), .little),
+            .GOT32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + A)), .little),
 
             else => try atom.reportUnhandledRelocError(rel, elf_file),
         }
@@ -1207,45 +1197,42 @@ const x86_64 = struct {
         rel: elf.Elf64_Rela,
         target: *const Symbol,
         args: ResolveArgs,
-        it: *RelocsIterator,
         code: []u8,
-        stream: anytype,
     ) !void {
         dev.check(.x86_64_backend);
-        _ = code;
-        _ = it;
         const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type());
-        const cwriter = stream.writer();
 
         _, const A, const S, const GOT, _, _, const DTP = args;
 
+        var writer: Writer = .fixed(code);
+
         switch (r_type) {
             .NONE => unreachable,
-            .@"8" => try cwriter.writeInt(u8, @as(u8, @bitCast(@as(i8, @intCast(S + A)))), .little),
-            .@"16" => try cwriter.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(S + A)))), .little),
-            .@"32" => try cwriter.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(S + A)))), .little),
-            .@"32S" => try cwriter.writeInt(i32, @as(i32, @intCast(S + A)), .little),
+            .@"8" => try writer.writeInt(u8, @as(u8, @bitCast(@as(i8, @intCast(S + A)))), .little),
+            .@"16" => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(S + A)))), .little),
+            .@"32" => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(S + A)))), .little),
+            .@"32S" => try writer.writeInt(i32, @as(i32, @intCast(S + A)), .little),
             .@"64" => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
-                try cwriter.writeInt(u64, value, .little)
+                try writer.writeInt(u64, value, .little)
             else
-                try cwriter.writeInt(i64, S + A, .little),
+                try writer.writeInt(i64, S + A, .little),
             .DTPOFF32 => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
-                try cwriter.writeInt(u64, value, .little)
+                try writer.writeInt(u64, value, .little)
             else
-                try cwriter.writeInt(i32, @as(i32, @intCast(S + A - DTP)), .little),
+                try writer.writeInt(i32, @as(i32, @intCast(S + A - DTP)), .little),
             .DTPOFF64 => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
-                try cwriter.writeInt(u64, value, .little)
+                try writer.writeInt(u64, value, .little)
             else
-                try cwriter.writeInt(i64, S + A - DTP, .little),
-            .GOTOFF64 => try cwriter.writeInt(i64, S + A - GOT, .little),
-            .GOTPC64 => try cwriter.writeInt(i64, GOT + A, .little),
+                try writer.writeInt(i64, S + A - DTP, .little),
+            .GOTOFF64 => try writer.writeInt(i64, S + A - GOT, .little),
+            .GOTPC64 => try writer.writeInt(i64, GOT + A, .little),
             .SIZE32 => {
                 const size = @as(i64, @intCast(target.elfSym(elf_file).st_size));
-                try cwriter.writeInt(u32, @bitCast(@as(i32, @intCast(size + A))), .little);
+                try writer.writeInt(u32, @bitCast(@as(i32, @intCast(size + A))), .little);
             },
             .SIZE64 => {
                 const size = @as(i64, @intCast(target.elfSym(elf_file).st_size));
-                try cwriter.writeInt(i64, @intCast(size + A), .little);
+                try writer.writeInt(i64, @intCast(size + A), .little);
             },
             else => try atom.reportUnhandledRelocError(rel, elf_file),
         }
@@ -1288,12 +1275,12 @@ const x86_64 = struct {
         rels: []const elf.Elf64_Rela,
         value: i32,
         elf_file: *Elf,
-        stream: anytype,
+        code: []u8,
+        r_offset: usize,
     ) !void {
         dev.check(.x86_64_backend);
         assert(rels.len == 2);
         const diags = &elf_file.base.comp.link_diags;
-        const writer = stream.writer();
         const rel: elf.R_X86_64 = @enumFromInt(rels[1].r_type());
         switch (rel) {
             .PC32,
@@ -1304,8 +1291,7 @@ const x86_64 = struct {
                     0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
                 };
                 std.mem.writeInt(i32, insts[12..][0..4], value - 12, .little);
-                try stream.seekBy(-4);
-                try writer.writeAll(&insts);
+                @memcpy(code[r_offset - 4 ..][0..insts.len], &insts);
             },
 
             else => {
@@ -1329,12 +1315,12 @@ const x86_64 = struct {
         rels: []const elf.Elf64_Rela,
         value: i32,
         elf_file: *Elf,
-        stream: anytype,
+        code: []u8,
+        r_offset: usize,
     ) !void {
         dev.check(.x86_64_backend);
         assert(rels.len == 2);
         const diags = &elf_file.base.comp.link_diags;
-        const writer = stream.writer();
         const rel: elf.R_X86_64 = @enumFromInt(rels[1].r_type());
         switch (rel) {
             .PC32,
@@ -1346,8 +1332,7 @@ const x86_64 = struct {
                     0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
                 };
                 std.mem.writeInt(i32, insts[8..][0..4], value, .little);
-                try stream.seekBy(-3);
-                try writer.writeAll(&insts);
+                @memcpy(code[r_offset - 3 ..][0..insts.len], &insts);
             },
 
             .GOTPCREL,
@@ -1360,8 +1345,7 @@ const x86_64 = struct {
                     0x90, // nop
                 };
                 std.mem.writeInt(i32, insts[8..][0..4], value, .little);
-                try stream.seekBy(-3);
-                try writer.writeAll(&insts);
+                @memcpy(code[r_offset - 3 ..][0..insts.len], &insts);
             },
 
             else => {
@@ -1390,7 +1374,7 @@ const x86_64 = struct {
                     // TODO: hack to force imm32s in the assembler
                     .{ .imm = .s(-129) },
                 }, t) catch return false;
-                var trash: std.io.Writer.Discarding = .init(&.{});
+                var trash: Writer.Discarding = .init(&.{});
                 inst.encode(&trash.writer, .{}) catch return false;
                 return true;
             },
@@ -1437,12 +1421,12 @@ const x86_64 = struct {
         rels: []const elf.Elf64_Rela,
         value: i32,
         elf_file: *Elf,
-        stream: anytype,
+        code: []u8,
+        r_offset: usize,
     ) !void {
         dev.check(.x86_64_backend);
         assert(rels.len == 2);
         const diags = &elf_file.base.comp.link_diags;
-        const writer = stream.writer();
         const rel: elf.R_X86_64 = @enumFromInt(rels[1].r_type());
         switch (rel) {
             .PC32,
@@ -1455,8 +1439,7 @@ const x86_64 = struct {
                     0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
                 };
                 std.mem.writeInt(i32, insts[12..][0..4], value, .little);
-                try stream.seekBy(-4);
-                try writer.writeAll(&insts);
+                @memcpy(code[r_offset - 4 ..][0..insts.len], &insts);
                 relocs_log.debug("    relaxing {f} and {f}", .{
                     relocation.fmtRelocType(rels[0].r_type(), .x86_64),
                     relocation.fmtRelocType(rels[1].r_type(), .x86_64),
@@ -1486,8 +1469,8 @@ const x86_64 = struct {
     }
 
     fn encode(insts: []const Instruction, code: []u8) !void {
-        var stream: std.io.Writer = .fixed(code);
-        for (insts) |inst| try inst.encode(&stream, .{});
+        var writer: Writer = .fixed(code);
+        for (insts) |inst| try inst.encode(&writer, .{});
     }
 
     const bits = @import("../../arch/x86_64/bits.zig");
@@ -1592,14 +1575,12 @@ const aarch64 = struct {
         args: ResolveArgs,
         it: *RelocsIterator,
         code_buffer: []u8,
-        stream: anytype,
     ) (error{ UnexpectedRemainder, DivisionByZero } || RelocError)!void {
         _ = it;
 
         const diags = &elf_file.base.comp.link_diags;
         const r_type: elf.R_AARCH64 = @enumFromInt(rel.r_type());
         const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
-        const cwriter = stream.writer();
         const code = code_buffer[r_offset..][0..4];
         const file_ptr = atom.file(elf_file).?;
 
@@ -1614,7 +1595,8 @@ const aarch64 = struct {
                     rel,
                     dynAbsRelocAction(target, elf_file),
                     elf_file,
-                    cwriter,
+                    code_buffer,
+                    r_offset,
                 );
             },
 
@@ -1782,25 +1764,20 @@ const aarch64 = struct {
         rel: elf.Elf64_Rela,
         target: *const Symbol,
         args: ResolveArgs,
-        it: *RelocsIterator,
         code: []u8,
-        stream: anytype,
     ) !void {
-        _ = it;
-        _ = code;
-
         const r_type: elf.R_AARCH64 = @enumFromInt(rel.r_type());
-        const cwriter = stream.writer();
 
         _, const A, const S, _, _, _, _ = args;
 
+        var writer: Writer = .fixed(code);
         switch (r_type) {
             .NONE => unreachable,
-            .ABS32 => try cwriter.writeInt(i32, @as(i32, @intCast(S + A)), .little),
+            .ABS32 => try writer.writeInt(i32, @as(i32, @intCast(S + A)), .little),
             .ABS64 => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
-                try cwriter.writeInt(u64, value, .little)
+                try writer.writeInt(u64, value, .little)
             else
-                try cwriter.writeInt(i64, S + A, .little),
+                try writer.writeInt(i64, S + A, .little),
             else => try atom.reportUnhandledRelocError(rel, elf_file),
         }
     }
@@ -1861,12 +1838,10 @@ const riscv = struct {
         args: ResolveArgs,
         it: *RelocsIterator,
         code: []u8,
-        stream: anytype,
     ) !void {
         const diags = &elf_file.base.comp.link_diags;
         const r_type: elf.R_RISCV = @enumFromInt(rel.r_type());
         const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
-        const cwriter = stream.writer();
 
         const P, const A, const S, const GOT, const G, const TP, const DTP = args;
         _ = TP;
@@ -1875,7 +1850,7 @@ const riscv = struct {
         switch (r_type) {
             .NONE => unreachable,
 
-            .@"32" => try cwriter.writeInt(u32, @as(u32, @truncate(@as(u64, @intCast(S + A)))), .little),
+            .@"32" => mem.writeInt(u32, code[r_offset..][0..4], @as(u32, @truncate(@as(u64, @intCast(S + A)))), .little),
 
             .@"64" => {
                 try atom.resolveDynAbsReloc(
@@ -1883,7 +1858,8 @@ const riscv = struct {
                     rel,
                     dynAbsRelocAction(target, elf_file),
                     elf_file,
-                    cwriter,
+                    code,
+                    r_offset,
                 );
             },
 
@@ -1997,15 +1973,9 @@ const riscv = struct {
         rel: elf.Elf64_Rela,
         target: *const Symbol,
         args: ResolveArgs,
-        it: *RelocsIterator,
         code: []u8,
-        stream: anytype,
     ) !void {
-        _ = it;
-
         const r_type: elf.R_RISCV = @enumFromInt(rel.r_type());
-        const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
-        const cwriter = stream.writer();
 
         _, const A, const S, const GOT, _, _, const DTP = args;
         _ = GOT;
@@ -2014,30 +1984,29 @@ const riscv = struct {
         switch (r_type) {
             .NONE => unreachable,
 
-            .@"32" => try cwriter.writeInt(i32, @as(i32, @intCast(S + A)), .little),
+            .@"32" => mem.writeInt(i32, code[0..4], @intCast(S + A), .little),
             .@"64" => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
-                try cwriter.writeInt(u64, value, .little)
+                mem.writeInt(u64, code[0..8], value, .little)
             else
-                try cwriter.writeInt(i64, S + A, .little),
-
-            .ADD8 => riscv_util.writeAddend(i8, .add, code[r_offset..][0..1], S + A),
-            .SUB8 => riscv_util.writeAddend(i8, .sub, code[r_offset..][0..1], S + A),
-            .ADD16 => riscv_util.writeAddend(i16, .add, code[r_offset..][0..2], S + A),
-            .SUB16 => riscv_util.writeAddend(i16, .sub, code[r_offset..][0..2], S + A),
-            .ADD32 => riscv_util.writeAddend(i32, .add, code[r_offset..][0..4], S + A),
-            .SUB32 => riscv_util.writeAddend(i32, .sub, code[r_offset..][0..4], S + A),
-            .ADD64 => riscv_util.writeAddend(i64, .add, code[r_offset..][0..8], S + A),
-            .SUB64 => riscv_util.writeAddend(i64, .sub, code[r_offset..][0..8], S + A),
-
-            .SET8 => mem.writeInt(i8, code[r_offset..][0..1], @as(i8, @truncate(S + A)), .little),
-            .SET16 => mem.writeInt(i16, code[r_offset..][0..2], @as(i16, @truncate(S + A)), .little),
-            .SET32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A)), .little),
-
-            .SET6 => riscv_util.writeSetSub6(.set, code[r_offset..][0..1], S + A),
-            .SUB6 => riscv_util.writeSetSub6(.sub, code[r_offset..][0..1], S + A),
-
-            .SET_ULEB128 => try riscv_util.writeSetSubUleb(.set, stream, S + A),
-            .SUB_ULEB128 => try riscv_util.writeSetSubUleb(.sub, stream, S - A),
+                mem.writeInt(i64, code[0..8], S + A, .little),
+            .ADD8 => riscv_util.writeAddend(i8, .add, code[0..1], S + A),
+            .SUB8 => riscv_util.writeAddend(i8, .sub, code[0..1], S + A),
+            .ADD16 => riscv_util.writeAddend(i16, .add, code[0..2], S + A),
+            .SUB16 => riscv_util.writeAddend(i16, .sub, code[0..2], S + A),
+            .ADD32 => riscv_util.writeAddend(i32, .add, code[0..4], S + A),
+            .SUB32 => riscv_util.writeAddend(i32, .sub, code[0..4], S + A),
+            .ADD64 => riscv_util.writeAddend(i64, .add, code[0..8], S + A),
+            .SUB64 => riscv_util.writeAddend(i64, .sub, code[0..8], S + A),
+
+            .SET8 => mem.writeInt(i8, code[0..1], @as(i8, @truncate(S + A)), .little),
+            .SET16 => mem.writeInt(i16, code[0..2], @as(i16, @truncate(S + A)), .little),
+            .SET32 => mem.writeInt(i32, code[0..4], @as(i32, @truncate(S + A)), .little),
+
+            .SET6 => riscv_util.writeSetSub6(.set, code[0..1], S + A),
+            .SUB6 => riscv_util.writeSetSub6(.sub, code[0..1], S + A),
+
+            .SET_ULEB128 => riscv_util.writeSetUleb(code, S + A),
+            .SUB_ULEB128 => riscv_util.writeSubUleb(code, S - A),
 
             else => try atom.reportUnhandledRelocError(rel, elf_file),
         }
@@ -2108,14 +2077,16 @@ pub const Extra = struct {
 const std = @import("std");
 const assert = std.debug.assert;
 const elf = std.elf;
-const eh_frame = @import("eh_frame.zig");
 const log = std.log.scoped(.link);
 const math = std.math;
 const mem = std.mem;
 const relocs_log = std.log.scoped(.link_relocs);
+const Allocator = mem.Allocator;
+const Writer = std.Io.Writer;
+
+const eh_frame = @import("eh_frame.zig");
 const relocation = @import("relocation.zig");
 
-const Allocator = mem.Allocator;
 const Atom = @This();
 const Elf = @import("../Elf.zig");
 const Fde = eh_frame.Fde;
src/link/Elf/AtomList.zig
@@ -89,7 +89,7 @@ pub fn allocate(list: *AtomList, elf_file: *Elf) !void {
     list.dirty = false;
 }
 
-pub fn write(list: AtomList, buffer: *std.array_list.Managed(u8), undefs: anytype, elf_file: *Elf) !void {
+pub fn write(list: AtomList, buffer: *std.Io.Writer.Allocating, undefs: anytype, elf_file: *Elf) !void {
     const gpa = elf_file.base.comp.gpa;
     const osec = elf_file.sections.items(.shdr)[list.output_section_index];
     assert(osec.sh_type != elf.SHT_NOBITS);
@@ -98,8 +98,7 @@ pub fn write(list: AtomList, buffer: *std.array_list.Managed(u8), undefs: anytyp
     log.debug("writing atoms in section '{s}'", .{elf_file.getShString(osec.sh_name)});
 
     const list_size = math.cast(usize, list.size) orelse return error.Overflow;
-    try buffer.ensureUnusedCapacity(list_size);
-    buffer.appendNTimesAssumeCapacity(0, list_size);
+    try buffer.writer.splatByteAll(0, list_size);
 
     for (list.atoms.keys()) |ref| {
         const atom_ptr = elf_file.atom(ref).?;
@@ -113,7 +112,7 @@ pub fn write(list: AtomList, buffer: *std.array_list.Managed(u8), undefs: anytyp
         const object = atom_ptr.file(elf_file).?.object;
         const code = try object.codeDecompressAlloc(elf_file, ref.index);
         defer gpa.free(code);
-        const out_code = buffer.items[off..][0..size];
+        const out_code = buffer.written()[off..][0..size];
         @memcpy(out_code, code);
 
         if (osec.sh_flags & elf.SHF_ALLOC == 0)
@@ -122,7 +121,7 @@ pub fn write(list: AtomList, buffer: *std.array_list.Managed(u8), undefs: anytyp
             try atom_ptr.resolveRelocsAlloc(elf_file, out_code);
     }
 
-    try elf_file.base.file.?.pwriteAll(buffer.items, list.offset(elf_file));
+    try elf_file.base.file.?.pwriteAll(buffer.written(), list.offset(elf_file));
     buffer.clearRetainingCapacity();
 }
 
src/link/Elf/gc.zig
@@ -162,22 +162,6 @@ fn prune(elf_file: *Elf) void {
     }
 }
 
-pub fn dumpPrunedAtoms(elf_file: *Elf) !void {
-    const stderr = std.fs.File.stderr().deprecatedWriter();
-    for (elf_file.objects.items) |index| {
-        const file = elf_file.file(index).?;
-        for (file.atoms()) |atom_index| {
-            const atom = file.atom(atom_index) orelse continue;
-            if (!atom.alive)
-                // TODO should we simply print to stderr?
-                try stderr.print("link: removing unused section '{s}' in file '{f}'\n", .{
-                    atom.name(elf_file),
-                    atom.file(elf_file).?.fmtPath(),
-                });
-        }
-    }
-}
-
 const Level = struct {
     value: usize = 0,
 
src/link/Elf/Object.zig
@@ -952,7 +952,7 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void {
         const is_tls = sym.type(elf_file) == elf.STT_TLS;
         const name = if (is_tls) ".tls_common" else ".common";
         const name_offset = @as(u32, @intCast(self.strtab.items.len));
-        try self.strtab.writer(gpa).print("{s}\x00", .{name});
+        try self.strtab.print(gpa, "{s}\x00", .{name});
 
         var sh_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE;
         if (is_tls) sh_flags |= elf.SHF_TLS;
src/link/Elf/relocatable.zig
@@ -100,32 +100,33 @@ pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
         state_log.debug("ar_strtab\n{f}\n", .{ar_strtab});
     }
 
-    var buffer = std.array_list.Managed(u8).init(gpa);
-    defer buffer.deinit();
-    try buffer.ensureTotalCapacityPrecise(total_size);
+    const buffer = try gpa.alloc(u8, total_size);
+    defer gpa.free(buffer);
+
+    var writer: std.Io.Writer = .fixed(buffer);
 
     // Write magic
-    try buffer.writer().writeAll(elf.ARMAG);
+    try writer.writeAll(elf.ARMAG);
 
     // Write symtab
-    try ar_symtab.write(.p64, elf_file, buffer.writer());
+    try ar_symtab.write(.p64, elf_file, &writer);
 
     // Write strtab
     if (ar_strtab.size() > 0) {
-        if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
-        try ar_strtab.write(buffer.writer());
+        if (!mem.isAligned(writer.end, 2)) try writer.writeByte(0);
+        try ar_strtab.write(&writer);
     }
 
     // Write object files
     for (files.items) |index| {
-        if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
-        try elf_file.file(index).?.writeAr(elf_file, buffer.writer());
+        if (!mem.isAligned(writer.end, 2)) try writer.writeByte(0);
+        try elf_file.file(index).?.writeAr(elf_file, &writer);
     }
 
-    assert(buffer.items.len == total_size);
+    assert(writer.buffered().len == total_size);
 
     try elf_file.base.file.?.setEndPos(total_size);
-    try elf_file.base.file.?.pwriteAll(buffer.items, 0);
+    try elf_file.base.file.?.pwriteAll(writer.buffered(), 0);
 
     if (diags.hasErrors()) return error.LinkFailure;
 }
@@ -407,15 +408,16 @@ fn writeSyntheticSections(elf_file: *Elf) !void {
         };
         const shdr = slice.items(.shdr)[shndx];
         const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, @intCast(sh_size - existing_size));
-        defer buffer.deinit();
-        try eh_frame.writeEhFrameRelocatable(elf_file, buffer.writer());
+        const buffer = try gpa.alloc(u8, @intCast(sh_size - existing_size));
+        defer gpa.free(buffer);
+        var writer: std.Io.Writer = .fixed(buffer);
+        try eh_frame.writeEhFrameRelocatable(elf_file, &writer);
         log.debug("writing .eh_frame from 0x{x} to 0x{x}", .{
             shdr.sh_offset + existing_size,
             shdr.sh_offset + sh_size,
         });
-        assert(buffer.items.len == sh_size - existing_size);
-        try elf_file.base.file.?.pwriteAll(buffer.items, shdr.sh_offset + existing_size);
+        assert(writer.buffered().len == sh_size - existing_size);
+        try elf_file.base.file.?.pwriteAll(writer.buffered(), shdr.sh_offset + existing_size);
     }
     if (elf_file.section_indexes.eh_frame_rela) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
@@ -446,15 +448,16 @@ fn writeGroups(elf_file: *Elf) !void {
     for (elf_file.group_sections.items) |cgs| {
         const shdr = elf_file.sections.items(.shdr)[cgs.shndx];
         const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, sh_size);
-        defer buffer.deinit();
-        try cgs.write(elf_file, buffer.writer());
-        assert(buffer.items.len == sh_size);
+        const buffer = try gpa.alloc(u8, sh_size);
+        defer gpa.free(buffer);
+        var writer: std.Io.Writer = .fixed(buffer);
+        try cgs.write(elf_file, &writer);
+        assert(writer.buffered().len == sh_size);
         log.debug("writing group from 0x{x} to 0x{x}", .{
             shdr.sh_offset,
             shdr.sh_offset + shdr.sh_size,
         });
-        try elf_file.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+        try elf_file.base.file.?.pwriteAll(writer.buffered(), shdr.sh_offset);
     }
 }
 
src/link/Elf/synthetic_sections.zig
@@ -94,134 +94,134 @@ pub const DynamicSection = struct {
         return nentries * @sizeOf(elf.Elf64_Dyn);
     }
 
-    pub fn write(dt: DynamicSection, elf_file: *Elf, writer: anytype) !void {
+    pub fn write(dt: DynamicSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
         const shdrs = elf_file.sections.items(.shdr);
 
         // NEEDED
         for (dt.needed.items) |off| {
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_NEEDED, .d_val = off });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_NEEDED, .d_val = off }), .little);
         }
 
         if (dt.soname) |off| {
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SONAME, .d_val = off });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_SONAME, .d_val = off }), .little);
         }
 
         // RUNPATH
         // TODO add option in Options to revert to old RPATH tag
         if (dt.rpath > 0) {
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RUNPATH, .d_val = dt.rpath });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_RUNPATH, .d_val = dt.rpath }), .little);
         }
 
         // INIT
         if (elf_file.sectionByName(".init")) |shndx| {
             const addr = shdrs[shndx].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT, .d_val = addr });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_INIT, .d_val = addr }), .little);
         }
 
         // FINI
         if (elf_file.sectionByName(".fini")) |shndx| {
             const addr = shdrs[shndx].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI, .d_val = addr });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FINI, .d_val = addr }), .little);
         }
 
         // INIT_ARRAY
         if (elf_file.sectionByName(".init_array")) |shndx| {
             const shdr = shdrs[shndx];
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT_ARRAY, .d_val = shdr.sh_addr });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT_ARRAYSZ, .d_val = shdr.sh_size });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_INIT_ARRAY, .d_val = shdr.sh_addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_INIT_ARRAYSZ, .d_val = shdr.sh_size }), .little);
         }
 
         // FINI_ARRAY
         if (elf_file.sectionByName(".fini_array")) |shndx| {
             const shdr = shdrs[shndx];
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI_ARRAY, .d_val = shdr.sh_addr });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI_ARRAYSZ, .d_val = shdr.sh_size });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FINI_ARRAY, .d_val = shdr.sh_addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FINI_ARRAYSZ, .d_val = shdr.sh_size }), .little);
         }
 
         // RELA
         if (elf_file.section_indexes.rela_dyn) |shndx| {
             const shdr = shdrs[shndx];
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELA, .d_val = shdr.sh_addr });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELASZ, .d_val = shdr.sh_size });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELAENT, .d_val = shdr.sh_entsize });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_RELA, .d_val = shdr.sh_addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_RELASZ, .d_val = shdr.sh_size }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_RELAENT, .d_val = shdr.sh_entsize }), .little);
         }
 
         // JMPREL
         if (elf_file.section_indexes.rela_plt) |shndx| {
             const shdr = shdrs[shndx];
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_JMPREL, .d_val = shdr.sh_addr });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTRELSZ, .d_val = shdr.sh_size });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTREL, .d_val = elf.DT_RELA });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_JMPREL, .d_val = shdr.sh_addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PLTRELSZ, .d_val = shdr.sh_size }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PLTREL, .d_val = elf.DT_RELA }), .little);
         }
 
         // PLTGOT
         if (elf_file.section_indexes.got_plt) |shndx| {
             const addr = shdrs[shndx].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTGOT, .d_val = addr });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_PLTGOT, .d_val = addr }), .little);
         }
 
         {
             assert(elf_file.section_indexes.hash != null);
             const addr = shdrs[elf_file.section_indexes.hash.?].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_HASH, .d_val = addr });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_HASH, .d_val = addr }), .little);
         }
 
         if (elf_file.section_indexes.gnu_hash) |shndx| {
             const addr = shdrs[shndx].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_GNU_HASH, .d_val = addr });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_GNU_HASH, .d_val = addr }), .little);
         }
 
         // TEXTREL
         if (elf_file.has_text_reloc) {
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_TEXTREL, .d_val = 0 });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_TEXTREL, .d_val = 0 }), .little);
         }
 
         // SYMTAB + SYMENT
         {
             assert(elf_file.section_indexes.dynsymtab != null);
             const shdr = shdrs[elf_file.section_indexes.dynsymtab.?];
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SYMTAB, .d_val = shdr.sh_addr });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SYMENT, .d_val = shdr.sh_entsize });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_SYMTAB, .d_val = shdr.sh_addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_SYMENT, .d_val = shdr.sh_entsize }), .little);
         }
 
         // STRTAB + STRSZ
         {
             assert(elf_file.section_indexes.dynstrtab != null);
             const shdr = shdrs[elf_file.section_indexes.dynstrtab.?];
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_STRTAB, .d_val = shdr.sh_addr });
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_STRSZ, .d_val = shdr.sh_size });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_STRTAB, .d_val = shdr.sh_addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_STRSZ, .d_val = shdr.sh_size }), .little);
         }
 
         // VERSYM
         if (elf_file.section_indexes.versym) |shndx| {
             const addr = shdrs[shndx].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_VERSYM, .d_val = addr });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_VERSYM, .d_val = addr }), .little);
         }
 
         // VERNEED + VERNEEDNUM
         if (elf_file.section_indexes.verneed) |shndx| {
             const addr = shdrs[shndx].sh_addr;
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_VERNEED, .d_val = addr });
-            try writer.writeStruct(elf.Elf64_Dyn{
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_VERNEED, .d_val = addr }), .little);
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{
                 .d_tag = elf.DT_VERNEEDNUM,
                 .d_val = elf_file.verneed.verneed.items.len,
-            });
+            }), .little);
         }
 
         // FLAGS
         if (dt.getFlags(elf_file)) |flags| {
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FLAGS, .d_val = flags });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FLAGS, .d_val = flags }), .little);
         }
         // FLAGS_1
         if (dt.getFlags1(elf_file)) |flags_1| {
-            try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FLAGS_1, .d_val = flags_1 });
+            try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_FLAGS_1, .d_val = flags_1 }), .little);
         }
 
         // DEBUG
-        if (!elf_file.isEffectivelyDynLib()) try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_DEBUG, .d_val = 0 });
+        if (!elf_file.isEffectivelyDynLib()) try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_DEBUG, .d_val = 0 }), .little);
 
         // NULL
-        try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_NULL, .d_val = 0 });
+        try writer.writeStruct(@as(elf.Elf64_Dyn, .{ .d_tag = elf.DT_NULL, .d_val = 0 }), .little);
     }
 };
 
@@ -360,7 +360,7 @@ pub const GotSection = struct {
         return s;
     }
 
-    pub fn write(got: GotSection, elf_file: *Elf, writer: anytype) !void {
+    pub fn write(got: GotSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
         const comp = elf_file.base.comp;
         const is_dyn_lib = elf_file.isEffectivelyDynLib();
         const apply_relocs = true; // TODO add user option for this
@@ -666,7 +666,7 @@ pub const PltSection = struct {
         };
     }
 
-    pub fn write(plt: PltSection, elf_file: *Elf, writer: anytype) !void {
+    pub fn write(plt: PltSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
         const cpu_arch = elf_file.getTarget().cpu.arch;
         switch (cpu_arch) {
             .x86_64 => try x86_64.write(plt, elf_file, writer),
@@ -763,7 +763,7 @@ pub const PltSection = struct {
     }
 
     const x86_64 = struct {
-        fn write(plt: PltSection, elf_file: *Elf, writer: anytype) !void {
+        fn write(plt: PltSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
             const shdrs = elf_file.sections.items(.shdr);
             const plt_addr = shdrs[elf_file.section_indexes.plt.?].sh_addr;
             const got_plt_addr = shdrs[elf_file.section_indexes.got_plt.?].sh_addr;
@@ -778,7 +778,7 @@ pub const PltSection = struct {
             disp = @as(i64, @intCast(got_plt_addr + 16)) - @as(i64, @intCast(plt_addr + 14)) - 4;
             mem.writeInt(i32, preamble[14..][0..4], @as(i32, @intCast(disp)), .little);
             try writer.writeAll(&preamble);
-            try writer.writeByteNTimes(0xcc, preambleSize(.x86_64) - preamble.len);
+            try writer.splatByteAll(0xcc, preambleSize(.x86_64) - preamble.len);
 
             for (plt.symbols.items, 0..) |ref, i| {
                 const sym = elf_file.symbol(ref).?;
@@ -798,7 +798,7 @@ pub const PltSection = struct {
     };
 
     const aarch64 = struct {
-        fn write(plt: PltSection, elf_file: *Elf, writer: anytype) !void {
+        fn write(plt: PltSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
             {
                 const shdrs = elf_file.sections.items(.shdr);
                 const plt_addr: i64 = @intCast(shdrs[elf_file.section_indexes.plt.?].sh_addr);
@@ -853,7 +853,7 @@ pub const GotPltSection = struct {
         return preamble_size + elf_file.plt.symbols.items.len * 8;
     }
 
-    pub fn write(got_plt: GotPltSection, elf_file: *Elf, writer: anytype) !void {
+    pub fn write(got_plt: GotPltSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
         _ = got_plt;
         {
             // [0]: _DYNAMIC
@@ -904,7 +904,7 @@ pub const PltGotSection = struct {
         };
     }
 
-    pub fn write(plt_got: PltGotSection, elf_file: *Elf, writer: anytype) !void {
+    pub fn write(plt_got: PltGotSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
         const cpu_arch = elf_file.getTarget().cpu.arch;
         switch (cpu_arch) {
             .x86_64 => try x86_64.write(plt_got, elf_file, writer),
@@ -940,7 +940,7 @@ pub const PltGotSection = struct {
     }
 
     const x86_64 = struct {
-        pub fn write(plt_got: PltGotSection, elf_file: *Elf, writer: anytype) !void {
+        pub fn write(plt_got: PltGotSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
             for (plt_got.symbols.items) |ref| {
                 const sym = elf_file.symbol(ref).?;
                 const target_addr = sym.gotAddress(elf_file);
@@ -958,7 +958,7 @@ pub const PltGotSection = struct {
     };
 
     const aarch64 = struct {
-        fn write(plt_got: PltGotSection, elf_file: *Elf, writer: anytype) !void {
+        fn write(plt_got: PltGotSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
             for (plt_got.symbols.items) |ref| {
                 const sym = elf_file.symbol(ref).?;
                 const target_addr = sym.gotAddress(elf_file);
@@ -1133,14 +1133,14 @@ pub const DynsymSection = struct {
         return @as(u32, @intCast(dynsym.entries.items.len + 1));
     }
 
-    pub fn write(dynsym: DynsymSection, elf_file: *Elf, writer: anytype) !void {
-        try writer.writeStruct(Elf.null_sym);
+    pub fn write(dynsym: DynsymSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
+        try writer.writeStruct(Elf.null_sym, .little);
         for (dynsym.entries.items) |entry| {
             const sym = elf_file.symbol(entry.ref).?;
             var out_sym: elf.Elf64_Sym = Elf.null_sym;
             sym.setOutputSym(elf_file, &out_sym);
             out_sym.st_name = entry.off;
-            try writer.writeStruct(out_sym);
+            try writer.writeStruct(out_sym, .little);
         }
     }
 };
@@ -1175,10 +1175,12 @@ pub const HashSection = struct {
         }
 
         try hs.buffer.ensureTotalCapacityPrecise(gpa, (2 + nsyms * 2) * 4);
-        hs.buffer.writer(gpa).writeInt(u32, @as(u32, @intCast(nsyms)), .little) catch unreachable;
-        hs.buffer.writer(gpa).writeInt(u32, @as(u32, @intCast(nsyms)), .little) catch unreachable;
-        hs.buffer.writer(gpa).writeAll(mem.sliceAsBytes(buckets)) catch unreachable;
-        hs.buffer.writer(gpa).writeAll(mem.sliceAsBytes(chains)) catch unreachable;
+        var w: std.Io.Writer = .fixed(hs.buffer.unusedCapacitySlice());
+        w.writeInt(u32, @as(u32, @intCast(nsyms)), .little) catch unreachable;
+        w.writeInt(u32, @as(u32, @intCast(nsyms)), .little) catch unreachable;
+        w.writeAll(@ptrCast(buckets)) catch unreachable;
+        w.writeAll(@ptrCast(chains)) catch unreachable;
+        hs.buffer.items.len += w.end;
     }
 
     pub inline fn size(hs: HashSection) usize {
@@ -1439,7 +1441,7 @@ pub const VerneedSection = struct {
         return vern.verneed.items.len * @sizeOf(elf.Elf64_Verneed) + vern.vernaux.items.len * @sizeOf(elf.Vernaux);
     }
 
-    pub fn write(vern: VerneedSection, writer: anytype) !void {
+    pub fn write(vern: VerneedSection, writer: *std.Io.Writer) !void {
         try writer.writeAll(mem.sliceAsBytes(vern.verneed.items));
         try writer.writeAll(mem.sliceAsBytes(vern.vernaux.items));
     }
@@ -1467,7 +1469,7 @@ pub const GroupSection = struct {
         return (members.len + 1) * @sizeOf(u32);
     }
 
-    pub fn write(cgs: GroupSection, elf_file: *Elf, writer: anytype) !void {
+    pub fn write(cgs: GroupSection, elf_file: *Elf, writer: *std.Io.Writer) !void {
         const cg = cgs.group(elf_file);
         const object = cg.file(elf_file).object;
         const members = cg.members(elf_file);
@@ -1495,7 +1497,7 @@ pub const GroupSection = struct {
     }
 };
 
-fn writeInt(value: anytype, elf_file: *Elf, writer: anytype) !void {
+fn writeInt(value: anytype, elf_file: *Elf, writer: *std.Io.Writer) !void {
     const entry_size = elf_file.archPtrWidthBytes();
     const target = elf_file.getTarget();
     const endian = target.cpu.arch.endian();
src/link/Elf.zig
@@ -811,10 +811,6 @@ fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void {
 
     if (self.base.gc_sections) {
         try gc.gcAtoms(self);
-
-        if (self.base.print_gc_sections) {
-            try gc.dumpPrunedAtoms(self);
-        }
     }
 
     self.checkDuplicates() catch |err| switch (err) {
@@ -3005,7 +3001,7 @@ fn writeAtoms(self: *Elf) !void {
         undefs.deinit();
     }
 
-    var buffer = std.array_list.Managed(u8).init(gpa);
+    var buffer: std.Io.Writer.Allocating = .init(gpa);
     defer buffer.deinit();
 
     const slice = self.sections.slice();
@@ -3032,9 +3028,9 @@ fn writeAtoms(self: *Elf) !void {
             try buffer.ensureUnusedCapacity(thunk_size);
             const shdr = slice.items(.shdr)[th.output_section_index];
             const offset = @as(u64, @intCast(th.value)) + shdr.sh_offset;
-            try th.write(self, buffer.writer());
-            assert(buffer.items.len == thunk_size);
-            try self.pwriteAll(buffer.items, offset);
+            try th.write(self, &buffer.writer);
+            assert(buffer.written().len == thunk_size);
+            try self.pwriteAll(buffer.written(), offset);
             buffer.clearRetainingCapacity();
         }
     }
@@ -3166,26 +3162,26 @@ fn writeSyntheticSections(self: *Elf) !void {
 
     if (self.section_indexes.verneed) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.verneed.size());
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.verneed.size());
         defer buffer.deinit();
-        try self.verneed.write(buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.verneed.write(&buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.dynamic) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.dynamic.size(self));
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.dynamic.size(self));
         defer buffer.deinit();
-        try self.dynamic.write(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.dynamic.write(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.dynsymtab) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.dynsym.size());
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.dynsym.size());
         defer buffer.deinit();
-        try self.dynsym.write(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.dynsym.write(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.dynstrtab) |shndx| {
@@ -3201,28 +3197,28 @@ fn writeSyntheticSections(self: *Elf) !void {
         };
         const shdr = slice.items(.shdr)[shndx];
         const sh_size = try self.cast(usize, shdr.sh_size);
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, @intCast(sh_size - existing_size));
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, @intCast(sh_size - existing_size));
         defer buffer.deinit();
-        try eh_frame.writeEhFrame(self, buffer.writer());
-        assert(buffer.items.len == sh_size - existing_size);
-        try self.pwriteAll(buffer.items, shdr.sh_offset + existing_size);
+        try eh_frame.writeEhFrame(self, &buffer.writer);
+        assert(buffer.written().len == sh_size - existing_size);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset + existing_size);
     }
 
     if (self.section_indexes.eh_frame_hdr) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
         const sh_size = try self.cast(usize, shdr.sh_size);
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, sh_size);
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, sh_size);
         defer buffer.deinit();
-        try eh_frame.writeEhFrameHdr(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try eh_frame.writeEhFrameHdr(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.got) |index| {
         const shdr = slice.items(.shdr)[index];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.got.size(self));
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.got.size(self));
         defer buffer.deinit();
-        try self.got.write(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.got.write(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.rela_dyn) |shndx| {
@@ -3235,26 +3231,26 @@ fn writeSyntheticSections(self: *Elf) !void {
 
     if (self.section_indexes.plt) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.plt.size(self));
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.plt.size(self));
         defer buffer.deinit();
-        try self.plt.write(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.plt.write(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.got_plt) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.got_plt.size(self));
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.got_plt.size(self));
         defer buffer.deinit();
-        try self.got_plt.write(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.got_plt.write(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.plt_got) |shndx| {
         const shdr = slice.items(.shdr)[shndx];
-        var buffer = try std.array_list.Managed(u8).initCapacity(gpa, self.plt_got.size(self));
+        var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.plt_got.size(self));
         defer buffer.deinit();
-        try self.plt_got.write(self, buffer.writer());
-        try self.pwriteAll(buffer.items, shdr.sh_offset);
+        try self.plt_got.write(self, &buffer.writer);
+        try self.pwriteAll(buffer.written(), shdr.sh_offset);
     }
 
     if (self.section_indexes.rela_plt) |shndx| {
@@ -3757,7 +3753,7 @@ pub fn insertShString(self: *Elf, name: [:0]const u8) error{OutOfMemory}!u32 {
     const gpa = self.base.comp.gpa;
     const off = @as(u32, @intCast(self.shstrtab.items.len));
     try self.shstrtab.ensureUnusedCapacity(gpa, name.len + 1);
-    self.shstrtab.writer(gpa).print("{s}\x00", .{name}) catch unreachable;
+    self.shstrtab.print(gpa, "{s}\x00", .{name}) catch unreachable;
     return off;
 }
 
@@ -3770,7 +3766,7 @@ pub fn insertDynString(self: *Elf, name: []const u8) error{OutOfMemory}!u32 {
     const gpa = self.base.comp.gpa;
     const off = @as(u32, @intCast(self.dynstrtab.items.len));
     try self.dynstrtab.ensureUnusedCapacity(gpa, name.len + 1);
-    self.dynstrtab.writer(gpa).print("{s}\x00", .{name}) catch unreachable;
+    self.dynstrtab.print(gpa, "{s}\x00", .{name}) catch unreachable;
     return off;
 }
 
src/link/riscv.zig
@@ -9,29 +9,28 @@ pub fn writeSetSub6(comptime op: enum { set, sub }, code: *[1]u8, addend: anytyp
     mem.writeInt(u8, code, value, .little);
 }
 
-pub fn writeSetSubUleb(comptime op: enum { set, sub }, stream: *std.io.FixedBufferStream([]u8), addend: i64) !void {
-    switch (op) {
-        .set => try overwriteUleb(stream, @intCast(addend)),
-        .sub => {
-            const position = try stream.getPos();
-            const value: u64 = try std.leb.readUleb128(u64, stream.reader());
-            try stream.seekTo(position);
-            try overwriteUleb(stream, value -% @as(u64, @intCast(addend)));
-        },
-    }
+pub fn writeSubUleb(code: []u8, addend: i64) void {
+    var reader: std.Io.Reader = .fixed(code);
+    const value = reader.takeLeb128(u64) catch unreachable;
+    overwriteUleb(code, value -% @as(u64, @intCast(addend)));
+}
+
+pub fn writeSetUleb(code: []u8, addend: i64) void {
+    overwriteUleb(code, @intCast(addend));
 }
 
-fn overwriteUleb(stream: *std.io.FixedBufferStream([]u8), addend: u64) !void {
+fn overwriteUleb(code: []u8, addend: u64) void {
     var value: u64 = addend;
-    const writer = stream.writer();
+    var i: usize = 0;
 
     while (true) {
-        const byte = stream.buffer[stream.pos];
+        const byte = code[i];
         if (byte & 0x80 == 0) break;
-        try writer.writeByte(0x80 | @as(u8, @truncate(value & 0x7f)));
+        code[i] = 0x80 | @as(u8, @truncate(value & 0x7f));
+        i += 1;
         value >>= 7;
     }
-    stream.buffer[stream.pos] = @truncate(value & 0x7f);
+    code[i] = @truncate(value & 0x7f);
 }
 
 pub fn writeAddend(