Commit 6dbf5f1d86

Luuk de Gram <luuk@degram.dev>
2022-09-10 19:38:19
wasm-linker: write to file at once
Rather than writing to the file using a writer, we now first write to an arraylist and store the binary in memory. Once the full binary data was written, we write all data to disk at once. This reduces the amount of syscalls tremendously, increasing the performance of the linker in exchange for increased memory usage during flush.
1 parent a01b144
Changed files (1)
src
src/link/Wasm.zig
@@ -653,6 +653,8 @@ fn resolveSymbolsInArchives(self: *Wasm) !void {
 }
 
 fn checkUndefinedSymbols(self: *const Wasm) !void {
+    if (self.base.options.output_mode == .Obj) return;
+
     var found_undefined_symbols = false;
     for (self.undefs.values()) |undef| {
         const symbol = undef.getSymbol(self);
@@ -2207,33 +2209,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     try self.mergeTypes();
     try self.setupExports();
 
-    const file = self.base.file.?;
     const header_size = 5 + 1;
     const is_obj = self.base.options.output_mode == .Obj;
 
+    var binary_bytes = std.ArrayList(u8).init(self.base.allocator);
+    defer binary_bytes.deinit();
+    const binary_writer = binary_bytes.writer();
+
     // We write the magic bytes at the end so they will only be written
-    // if everything succeeded as expected.
-    try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
-    try file.seekTo(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
+    // if everything succeeded as expected. So populate with 0's for now.
+    try binary_writer.writeAll(&[_]u8{0} ** 8);
 
     // Type section
     if (self.func_types.items.len != 0) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
         log.debug("Writing type section. Count: ({d})", .{self.func_types.items.len});
         for (self.func_types.items) |func_type| {
-            try leb.writeULEB128(writer, wasm.function_type);
-            try leb.writeULEB128(writer, @intCast(u32, func_type.params.len));
-            for (func_type.params) |param_ty| try leb.writeULEB128(writer, wasm.valtype(param_ty));
-            try leb.writeULEB128(writer, @intCast(u32, func_type.returns.len));
-            for (func_type.returns) |ret_ty| try leb.writeULEB128(writer, wasm.valtype(ret_ty));
+            try leb.writeULEB128(binary_writer, wasm.function_type);
+            try leb.writeULEB128(binary_writer, @intCast(u32, func_type.params.len));
+            for (func_type.params) |param_ty| {
+                try leb.writeULEB128(binary_writer, wasm.valtype(param_ty));
+            }
+            try leb.writeULEB128(binary_writer, @intCast(u32, func_type.returns.len));
+            for (func_type.returns) |ret_ty| {
+                try leb.writeULEB128(binary_writer, wasm.valtype(ret_ty));
+            }
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .type,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @intCast(u32, self.func_types.items.len),
         );
         section_count += 1;
@@ -2243,8 +2250,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     const import_memory = self.base.options.import_memory or is_obj;
     const import_table = self.base.options.import_table or is_obj;
     if (self.imports.count() != 0 or import_memory or import_table) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
 
         // import table is always first table so emit that first
         if (import_table) {
@@ -2261,14 +2267,14 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
                     },
                 },
             };
-            try self.emitImport(writer, table_imp);
+            try self.emitImport(binary_writer, table_imp);
         }
 
         var it = self.imports.iterator();
         while (it.next()) |entry| {
             assert(entry.key_ptr.*.getSymbol(self).isUndefined());
             const import = entry.value_ptr.*;
-            try self.emitImport(writer, import);
+            try self.emitImport(binary_writer, import);
         }
 
         if (import_memory) {
@@ -2278,14 +2284,14 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
                 .name = try self.string_table.put(self.base.allocator, mem_name),
                 .kind = .{ .memory = self.memories.limits },
             };
-            try self.emitImport(writer, mem_imp);
+            try self.emitImport(binary_writer, mem_imp);
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .import,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @intCast(u32, self.imports.count() + @boolToInt(import_memory) + @boolToInt(import_table)),
         );
         section_count += 1;
@@ -2293,17 +2299,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
 
     // Function section
     if (self.functions.count() != 0) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
         for (self.functions.values()) |function| {
-            try leb.writeULEB128(writer, function.type_index);
+            try leb.writeULEB128(binary_writer, function.type_index);
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .function,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @intCast(u32, self.functions.count()),
         );
         section_count += 1;
@@ -2312,20 +2317,19 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     // Table section
     const export_table = self.base.options.export_table;
     if (!import_table and self.function_table.count() != 0) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
 
-        try leb.writeULEB128(writer, wasm.reftype(.funcref));
-        try emitLimits(writer, .{
+        try leb.writeULEB128(binary_writer, wasm.reftype(.funcref));
+        try emitLimits(binary_writer, .{
             .min = @intCast(u32, self.function_table.count()) + 1,
             .max = null,
         });
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .table,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @as(u32, 1),
         );
         section_count += 1;
@@ -2333,15 +2337,14 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
 
     // Memory section
     if (!import_memory) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
 
-        try emitLimits(writer, self.memories.limits);
+        try emitLimits(binary_writer, self.memories.limits);
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .memory,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @as(u32, 1), // wasm currently only supports 1 linear memory segment
         );
         section_count += 1;
@@ -2349,20 +2352,19 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
 
     // Global section (used to emit stack pointer)
     if (self.wasm_globals.items.len > 0) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
 
         for (self.wasm_globals.items) |global| {
-            try writer.writeByte(wasm.valtype(global.global_type.valtype));
-            try writer.writeByte(@boolToInt(global.global_type.mutable));
-            try emitInit(writer, global.init);
+            try binary_writer.writeByte(wasm.valtype(global.global_type.valtype));
+            try binary_writer.writeByte(@boolToInt(global.global_type.mutable));
+            try emitInit(binary_writer, global.init);
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .global,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @intCast(u32, self.wasm_globals.items.len),
         );
         section_count += 1;
@@ -2370,35 +2372,35 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
 
     // Export section
     if (self.exports.items.len != 0 or export_table or !import_memory) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
+
         for (self.exports.items) |exp| {
             const name = self.string_table.get(exp.name);
-            try leb.writeULEB128(writer, @intCast(u32, name.len));
-            try writer.writeAll(name);
-            try leb.writeULEB128(writer, @enumToInt(exp.kind));
-            try leb.writeULEB128(writer, exp.index);
+            try leb.writeULEB128(binary_writer, @intCast(u32, name.len));
+            try binary_writer.writeAll(name);
+            try leb.writeULEB128(binary_writer, @enumToInt(exp.kind));
+            try leb.writeULEB128(binary_writer, exp.index);
         }
 
         if (export_table) {
-            try leb.writeULEB128(writer, @intCast(u32, "__indirect_function_table".len));
-            try writer.writeAll("__indirect_function_table");
-            try writer.writeByte(wasm.externalKind(.table));
-            try leb.writeULEB128(writer, @as(u32, 0)); // function table is always the first table
+            try leb.writeULEB128(binary_writer, @intCast(u32, "__indirect_function_table".len));
+            try binary_writer.writeAll("__indirect_function_table");
+            try binary_writer.writeByte(wasm.externalKind(.table));
+            try leb.writeULEB128(binary_writer, @as(u32, 0)); // function table is always the first table
         }
 
         if (!import_memory) {
-            try leb.writeULEB128(writer, @intCast(u32, "memory".len));
-            try writer.writeAll("memory");
-            try writer.writeByte(wasm.externalKind(.memory));
-            try leb.writeULEB128(writer, @as(u32, 0));
+            try leb.writeULEB128(binary_writer, @intCast(u32, "memory".len));
+            try binary_writer.writeAll("memory");
+            try binary_writer.writeByte(wasm.externalKind(.memory));
+            try leb.writeULEB128(binary_writer, @as(u32, 0));
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .@"export",
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @intCast(u32, self.exports.items.len) + @boolToInt(export_table) + @boolToInt(!import_memory),
         );
         section_count += 1;
@@ -2406,25 +2408,24 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
 
     // element section (function table)
     if (self.function_table.count() > 0) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
 
         var flags: u32 = 0x2; // Yes we have a table
-        try leb.writeULEB128(writer, flags);
-        try leb.writeULEB128(writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
-        try emitInit(writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
-        try leb.writeULEB128(writer, @as(u8, 0));
-        try leb.writeULEB128(writer, @intCast(u32, self.function_table.count()));
+        try leb.writeULEB128(binary_writer, flags);
+        try leb.writeULEB128(binary_writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
+        try emitInit(binary_writer, .{ .i32_const = 1 }); // We start at index 1, so unresolved function pointers are invalid
+        try leb.writeULEB128(binary_writer, @as(u8, 0));
+        try leb.writeULEB128(binary_writer, @intCast(u32, self.function_table.count()));
         var symbol_it = self.function_table.keyIterator();
         while (symbol_it.next()) |symbol_loc_ptr| {
-            try leb.writeULEB128(writer, symbol_loc_ptr.*.getSymbol(self).index);
+            try leb.writeULEB128(binary_writer, symbol_loc_ptr.*.getSymbol(self).index);
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .element,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @as(u32, 1),
         );
         section_count += 1;
@@ -2433,8 +2434,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     // Code section
     var code_section_size: u32 = 0;
     if (self.code_section_index) |code_index| {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
         var atom: *Atom = self.atoms.get(code_index).?.getFirst();
 
         // The code section must be sorted in line with the function order.
@@ -2460,13 +2460,13 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         std.sort.sort(*Atom, sorted_atoms.items, self, atom_sort_fn);
 
         for (sorted_atoms.items) |sorted_atom| {
-            try leb.writeULEB128(writer, sorted_atom.size);
-            try writer.writeAll(sorted_atom.code.items);
+            try leb.writeULEB128(binary_writer, sorted_atom.size);
+            try binary_writer.writeAll(sorted_atom.code.items);
         }
 
-        code_section_size = @intCast(u32, (try file.getPos()) - header_offset - header_size);
+        code_section_size = @intCast(u32, binary_bytes.items.len - header_offset - header_size);
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .code,
             code_section_size,
@@ -2478,8 +2478,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
 
     // Data section
     if (self.data_segments.count() != 0) {
-        const header_offset = try reserveVecSectionHeader(file);
-        const writer = file.writer();
+        const header_offset = try reserveVecSectionHeader(&binary_bytes);
 
         var it = self.data_segments.iterator();
         var segment_count: u32 = 0;
@@ -2493,10 +2492,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
             const segment = self.segments.items[atom_index];
 
             // flag and index to memory section (currently, there can only be 1 memory section in wasm)
-            try leb.writeULEB128(writer, @as(u32, 0));
+            try leb.writeULEB128(binary_writer, @as(u32, 0));
             // offset into data section
-            try emitInit(writer, .{ .i32_const = @bitCast(i32, segment.offset) });
-            try leb.writeULEB128(writer, segment.size);
+            try emitInit(binary_writer, .{ .i32_const = @bitCast(i32, segment.offset) });
+            try leb.writeULEB128(binary_writer, segment.size);
 
             // fill in the offset table and the data segments
             var current_offset: u32 = 0;
@@ -2508,12 +2507,12 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
                 // Pad with zeroes to ensure all segments are aligned
                 if (current_offset != atom.offset) {
                     const diff = atom.offset - current_offset;
-                    try writer.writeByteNTimes(0, diff);
+                    try binary_writer.writeByteNTimes(0, diff);
                     current_offset += diff;
                 }
                 assert(current_offset == atom.offset);
                 assert(atom.code.items.len == atom.size);
-                try writer.writeAll(atom.code.items);
+                try binary_writer.writeAll(atom.code.items);
 
                 current_offset += atom.size;
                 if (atom.next) |next| {
@@ -2522,7 +2521,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
                     // also pad with zeroes when last atom to ensure
                     // segments are aligned.
                     if (current_offset != segment.size) {
-                        try writer.writeByteNTimes(0, segment.size - current_offset);
+                        try binary_writer.writeByteNTimes(0, segment.size - current_offset);
                         current_offset += segment.size - current_offset;
                     }
                     break;
@@ -2532,10 +2531,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         }
 
         try writeVecSectionHeader(
-            file,
+            binary_bytes.items,
             header_offset,
             .data,
-            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, binary_bytes.items.len - header_offset - header_size),
             @intCast(u32, segment_count),
         );
         data_section_index = section_count;
@@ -2547,12 +2546,12 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
         // we never store all symbols in a single table, but store a location reference instead.
         // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections.
         var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena);
-        try self.emitLinkSection(file, arena, &symbol_table);
+        try self.emitLinkSection(&binary_bytes, &symbol_table);
         if (code_section_index) |code_index| {
-            try self.emitCodeRelocations(file, arena, code_index, symbol_table);
+            try self.emitCodeRelocations(&binary_bytes, code_index, symbol_table);
         }
         if (data_section_index) |data_index| {
-            try self.emitDataRelocations(file, arena, data_index, symbol_table);
+            try self.emitDataRelocations(&binary_bytes, data_index, symbol_table);
         }
     } else if (!self.base.options.strip) {
         if (self.dwarf) |*dwarf| {
@@ -2592,41 +2591,45 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
                     try debug_bytes.appendSlice(atom.code.items);
                     atom = atom.next orelse break;
                 }
-                try emitDebugSection(file, debug_bytes.items, item.name);
+                try emitDebugSection(&binary_bytes, debug_bytes.items, item.name);
                 debug_bytes.clearRetainingCapacity();
             }
         }
-        try self.emitNameSection(file, arena);
+        try self.emitNameSection(&binary_bytes, arena);
     }
 
     // Only when writing all sections executed properly we write the magic
     // bytes. This allows us to easily detect what went wrong while generating
     // the final binary.
-    try file.pwriteAll(&(wasm.magic ++ wasm.version), 0);
+    mem.copy(u8, binary_bytes.items, &(wasm.magic ++ wasm.version));
+
+    // finally, write the entire binary into the file.
+    var iovec = [_]std.os.iovec_const{.{
+        .iov_base = binary_bytes.items.ptr,
+        .iov_len = binary_bytes.items.len,
+    }};
+    try self.base.file.?.writevAll(&iovec);
 }
 
-fn emitDebugSection(file: fs.File, data: []const u8, name: []const u8) !void {
+fn emitDebugSection(binary_bytes: *std.ArrayList(u8), data: []const u8, name: []const u8) !void {
     if (data.len == 0) return;
-    const header_offset = try reserveCustomSectionHeader(file);
-    const writer = file.writer();
+    const header_offset = try reserveCustomSectionHeader(binary_bytes);
+    const writer = binary_bytes.writer();
     try leb.writeULEB128(writer, @intCast(u32, name.len));
     try writer.writeAll(name);
 
-    try file.writevAll(&[_]std.os.iovec_const{.{
-        .iov_base = data.ptr,
-        .iov_len = data.len,
-    }});
-    const start = header_offset + 6 + name.len + getULEB128Size(@intCast(u32, name.len));
+    const start = binary_bytes.items.len - header_offset;
     log.debug("Emit debug section: '{s}' start=0x{x:0>8} end=0x{x:0>8}", .{ name, start, start + data.len });
+    try writer.writeAll(data);
 
     try writeCustomSectionHeader(
-        file,
+        binary_bytes.items,
         header_offset,
-        @intCast(u32, (try file.getPos()) - header_offset - 6),
+        @intCast(u32, binary_bytes.items.len - header_offset - 6),
     );
 }
 
-fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
+fn emitNameSection(self: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem.Allocator) !void {
     const Name = struct {
         index: u32,
         name: []const u8,
@@ -2672,8 +2675,8 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
     std.sort.sort(Name, funcs.values(), {}, Name.lessThan);
     std.sort.sort(Name, globals.items, {}, Name.lessThan);
 
-    const header_offset = try reserveCustomSectionHeader(file);
-    const writer = file.writer();
+    const header_offset = try reserveCustomSectionHeader(binary_bytes);
+    const writer = binary_bytes.writer();
     try leb.writeULEB128(writer, @intCast(u32, "name".len));
     try writer.writeAll("name");
 
@@ -2682,9 +2685,9 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
     try self.emitNameSubsection(.data_segment, segments.items, writer);
 
     try writeCustomSectionHeader(
-        file,
+        binary_bytes.items,
         header_offset,
-        @intCast(u32, (try file.getPos()) - header_offset - 6),
+        @intCast(u32, binary_bytes.items.len - header_offset - 6),
     );
 }
 
@@ -3197,54 +3200,40 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
     }
 }
 
-fn reserveVecSectionHeader(file: fs.File) !u64 {
+fn reserveVecSectionHeader(bytes: *std.ArrayList(u8)) !u32 {
     // section id + fixed leb contents size + fixed leb vector length
     const header_size = 1 + 5 + 5;
-    // TODO: this should be a single lseek(2) call, but fs.File does not
-    // currently provide a way to do this.
-    try file.seekBy(header_size);
-    return (try file.getPos()) - header_size;
+    const offset = @intCast(u32, bytes.items.len);
+    try bytes.appendSlice(&[_]u8{0} ** header_size);
+    return offset;
 }
 
-fn reserveCustomSectionHeader(file: fs.File) !u64 {
+fn reserveCustomSectionHeader(bytes: *std.ArrayList(u8)) !u64 {
     // unlike regular section, we don't emit the count
     const header_size = 1 + 5;
-    // TODO: this should be a single lseek(2) call, but fs.File does not
-    // currently provide a way to do this.
-    try file.seekBy(header_size);
-    return (try file.getPos()) - header_size;
+    const offset = @intCast(u32, bytes.items.len);
+    try bytes.appendSlice(&[_]u8{0} ** header_size);
+    return offset;
 }
 
-fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size: u32, items: u32) !void {
+fn writeVecSectionHeader(buffer: []u8, offset: u32, section: wasm.Section, size: u32, items: u32) !void {
     var buf: [1 + 5 + 5]u8 = undefined;
     buf[0] = @enumToInt(section);
     leb.writeUnsignedFixed(5, buf[1..6], size);
     leb.writeUnsignedFixed(5, buf[6..], items);
-
-    if (builtin.target.os.tag == .windows) {
-        // https://github.com/ziglang/zig/issues/12783
-        const curr_pos = try file.getPos();
-        try file.pwriteAll(&buf, offset);
-        try file.seekTo(curr_pos);
-    } else try file.pwriteAll(&buf, offset);
+    mem.copy(u8, buffer[offset..], &buf);
 }
 
-fn writeCustomSectionHeader(file: fs.File, offset: u64, size: u32) !void {
+fn writeCustomSectionHeader(buffer: []u8, offset: u64, size: u32) !void {
     var buf: [1 + 5]u8 = undefined;
     buf[0] = 0; // 0 = 'custom' section
     leb.writeUnsignedFixed(5, buf[1..6], size);
-
-    if (builtin.target.os.tag == .windows) {
-        // https://github.com/ziglang/zig/issues/12783
-        const curr_pos = try file.getPos();
-        try file.pwriteAll(&buf, offset);
-        try file.seekTo(curr_pos);
-    } else try file.pwriteAll(&buf, offset);
+    mem.copy(u8, buffer[offset..], &buf);
 }
 
-fn emitLinkSection(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
-    const offset = try reserveCustomSectionHeader(file);
-    const writer = file.writer();
+fn emitLinkSection(self: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
+    const offset = try reserveCustomSectionHeader(binary_bytes);
+    const writer = binary_bytes.writer();
     // emit "linking" custom section name
     const section_name = "linking";
     try leb.writeULEB128(writer, section_name.len);
@@ -3255,21 +3244,18 @@ fn emitLinkSection(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *
 
     // For each subsection type (found in types.Subsection) we can emit a section.
     // Currently, we only support emitting segment info and the symbol table.
-    try self.emitSymbolTable(file, arena, symbol_table);
-    try self.emitSegmentInfo(file, arena);
+    try self.emitSymbolTable(binary_bytes, symbol_table);
+    try self.emitSegmentInfo(binary_bytes);
 
-    const size = @intCast(u32, (try file.getPos()) - offset - 6);
-    try writeCustomSectionHeader(file, offset, size);
+    const size = @intCast(u32, binary_bytes.items.len - offset - 6);
+    try writeCustomSectionHeader(binary_bytes.items, offset, size);
 }
 
-fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
-    // After emitting the subtype, we must emit the subsection's length
-    // so first write it to a temporary arraylist to calculate the length
-    // and then write all data at once.
-    var payload = std.ArrayList(u8).init(arena);
-    const writer = payload.writer();
+fn emitSymbolTable(self: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
+    const writer = binary_bytes.writer();
 
-    try leb.writeULEB128(file.writer(), @enumToInt(types.SubsectionType.WASM_SYMBOL_TABLE));
+    try leb.writeULEB128(writer, @enumToInt(types.SubsectionType.WASM_SYMBOL_TABLE));
+    const table_offset = binary_bytes.items.len;
 
     var symbol_count: u32 = 0;
     for (self.resolved_symbols.keys()) |sym_loc| {
@@ -3307,23 +3293,17 @@ fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *
         }
     }
 
-    var buf: [5]u8 = undefined;
-    leb.writeUnsignedFixed(5, &buf, symbol_count);
-    try payload.insertSlice(0, &buf);
-    try leb.writeULEB128(file.writer(), @intCast(u32, payload.items.len));
-
-    const iovec: std.os.iovec_const = .{
-        .iov_base = payload.items.ptr,
-        .iov_len = payload.items.len,
-    };
-    var iovecs = [_]std.os.iovec_const{iovec};
-    try file.writevAll(&iovecs);
+    var buf: [10]u8 = undefined;
+    leb.writeUnsignedFixed(5, buf[0..5], @intCast(u32, binary_bytes.items.len - table_offset + 5));
+    leb.writeUnsignedFixed(5, buf[5..], symbol_count);
+    try binary_bytes.insertSlice(table_offset, &buf);
 }
 
-fn emitSegmentInfo(self: *Wasm, file: fs.File, arena: Allocator) !void {
-    var payload = std.ArrayList(u8).init(arena);
-    const writer = payload.writer();
-    try leb.writeULEB128(file.writer(), @enumToInt(types.SubsectionType.WASM_SEGMENT_INFO));
+fn emitSegmentInfo(self: *Wasm, binary_bytes: *std.ArrayList(u8)) !void {
+    const writer = binary_bytes.writer();
+    try leb.writeULEB128(writer, @enumToInt(types.SubsectionType.WASM_SEGMENT_INFO));
+    const segment_offset = binary_bytes.items.len;
+
     try leb.writeULEB128(writer, @intCast(u32, self.segment_info.count()));
     for (self.segment_info.values()) |segment_info| {
         log.debug("Emit segment: {s} align({d}) flags({b})", .{
@@ -3337,13 +3317,9 @@ fn emitSegmentInfo(self: *Wasm, file: fs.File, arena: Allocator) !void {
         try leb.writeULEB128(writer, segment_info.flags);
     }
 
-    try leb.writeULEB128(file.writer(), @intCast(u32, payload.items.len));
-    const iovec: std.os.iovec_const = .{
-        .iov_base = payload.items.ptr,
-        .iov_len = payload.items.len,
-    };
-    var iovecs = [_]std.os.iovec_const{iovec};
-    try file.writevAll(&iovecs);
+    var buf: [5]u8 = undefined;
+    leb.writeUnsignedFixed(5, &buf, @intCast(u32, binary_bytes.items.len - segment_offset));
+    try binary_bytes.insertSlice(segment_offset, &buf);
 }
 
 pub fn getULEB128Size(uint_value: anytype) u32 {
@@ -3361,21 +3337,20 @@ pub fn getULEB128Size(uint_value: anytype) u32 {
 /// For each relocatable section, emits a custom "relocation.<section_name>" section
 fn emitCodeRelocations(
     self: *Wasm,
-    file: fs.File,
-    arena: Allocator,
+    binary_bytes: *std.ArrayList(u8),
     section_index: u32,
     symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
 ) !void {
     const code_index = self.code_section_index orelse return;
-    var payload = std.ArrayList(u8).init(arena);
-    const writer = payload.writer();
+    const writer = binary_bytes.writer();
+    const header_offset = try reserveCustomSectionHeader(binary_bytes);
 
     // write custom section information
     const name = "reloc.CODE";
     try leb.writeULEB128(writer, @intCast(u32, name.len));
     try writer.writeAll(name);
     try leb.writeULEB128(writer, section_index);
-    const reloc_start = payload.items.len;
+    const reloc_start = binary_bytes.items.len;
 
     var count: u32 = 0;
     var atom: *Atom = self.atoms.get(code_index).?.getFirst();
@@ -3401,36 +3376,27 @@ fn emitCodeRelocations(
     if (count == 0) return;
     var buf: [5]u8 = undefined;
     leb.writeUnsignedFixed(5, &buf, count);
-    try payload.insertSlice(reloc_start, &buf);
-    var iovecs = [_]std.os.iovec_const{
-        .{
-            .iov_base = payload.items.ptr,
-            .iov_len = payload.items.len,
-        },
-    };
-    const header_offset = try reserveCustomSectionHeader(file);
-    try file.writevAll(&iovecs);
-    const size = @intCast(u32, payload.items.len);
-    try writeCustomSectionHeader(file, header_offset, size);
+    try binary_bytes.insertSlice(reloc_start, &buf);
+    const size = @intCast(u32, binary_bytes.items.len - header_offset - 6);
+    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
 }
 
 fn emitDataRelocations(
     self: *Wasm,
-    file: fs.File,
-    arena: Allocator,
+    binary_bytes: *std.ArrayList(u8),
     section_index: u32,
     symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
 ) !void {
     if (self.data_segments.count() == 0) return;
-    var payload = std.ArrayList(u8).init(arena);
-    const writer = payload.writer();
+    const writer = binary_bytes.writer();
+    const header_offset = try reserveCustomSectionHeader(binary_bytes);
 
     // write custom section information
     const name = "reloc.DATA";
     try leb.writeULEB128(writer, @intCast(u32, name.len));
     try writer.writeAll(name);
     try leb.writeULEB128(writer, section_index);
-    const reloc_start = payload.items.len;
+    const reloc_start = binary_bytes.items.len;
 
     var count: u32 = 0;
     // for each atom, we calculate the uleb size and append that
@@ -3462,17 +3428,9 @@ fn emitDataRelocations(
 
     var buf: [5]u8 = undefined;
     leb.writeUnsignedFixed(5, &buf, count);
-    try payload.insertSlice(reloc_start, &buf);
-    var iovecs = [_]std.os.iovec_const{
-        .{
-            .iov_base = payload.items.ptr,
-            .iov_len = payload.items.len,
-        },
-    };
-    const header_offset = try reserveCustomSectionHeader(file);
-    try file.writevAll(&iovecs);
-    const size = @intCast(u32, payload.items.len);
-    try writeCustomSectionHeader(file, header_offset, size);
+    try binary_bytes.insertSlice(reloc_start, &buf);
+    const size = @intCast(u32, binary_bytes.items.len - header_offset - 6);
+    try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
 }
 
 /// Searches for an a matching function signature, when not found