zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 3c3cd4ee (36624B) - Raw


      1 const Wasm = @This();
      2 
      3 const std = @import("std");
      4 const builtin = @import("builtin");
      5 const mem = std.mem;
      6 const Allocator = std.mem.Allocator;
      7 const assert = std.debug.assert;
      8 const fs = std.fs;
      9 const leb = std.leb;
     10 const log = std.log.scoped(.link);
     11 const wasm = std.wasm;
     12 
     13 const Module = @import("../Module.zig");
     14 const Compilation = @import("../Compilation.zig");
     15 const codegen = @import("../codegen/wasm.zig");
     16 const link = @import("../link.zig");
     17 const trace = @import("../tracy.zig").trace;
     18 const build_options = @import("build_options");
     19 const wasi_libc = @import("../wasi_libc.zig");
     20 const Cache = @import("../Cache.zig");
     21 const TypedValue = @import("../TypedValue.zig");
     22 const LlvmObject = @import("../codegen/llvm.zig").Object;
     23 const Air = @import("../Air.zig");
     24 const Liveness = @import("../Liveness.zig");
     25 
     26 pub const base_tag = link.File.Tag.wasm;
     27 
     28 base: link.File,
     29 /// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
     30 llvm_object: ?*LlvmObject = null,
     31 /// List of all function Decls to be written to the output file. The index of
     32 /// each Decl in this list at the time of writing the binary is used as the
     33 /// function index. In the event where ext_funcs' size is not 0, the index of
     34 /// each function is added on top of the ext_funcs' length.
     35 /// TODO: can/should we access some data structure in Module directly?
     36 funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
     37 /// List of all extern function Decls to be written to the `import` section of the
     38 /// wasm binary. The positin in the list defines the function index
     39 ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
     40 /// When importing objects from the host environment, a name must be supplied.
     41 /// LLVM uses "env" by default when none is given. This would be a good default for Zig
     42 /// to support existing code.
     43 /// TODO: Allow setting this through a flag?
     44 host_name: []const u8 = "env",
     45 /// The last `DeclBlock` that was initialized will be saved here.
     46 last_block: ?*DeclBlock = null,
     47 /// Table with offsets, each element represents an offset with the value being
     48 /// the offset into the 'data' section where the data lives
     49 offset_table: std.ArrayListUnmanaged(u32) = .{},
     50 /// List of offset indexes which are free to be used for new decl's.
     51 /// Each element's value points to an index into the offset_table.
     52 offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
     53 /// List of all `Decl` that are currently alive.
     54 /// This is ment for bookkeeping so we can safely cleanup all codegen memory
     55 /// when calling `deinit`
     56 symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
     57 
     58 pub const FnData = struct {
     59     /// Generated code for the type of the function
     60     functype: std.ArrayListUnmanaged(u8),
     61     /// Generated code for the body of the function
     62     code: std.ArrayListUnmanaged(u8),
     63     /// Locations in the generated code where function indexes must be filled in.
     64     /// This must be kept ordered by offset.
     65     idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
     66 
     67     pub const empty: FnData = .{
     68         .functype = .{},
     69         .code = .{},
     70         .idx_refs = .{},
     71     };
     72 };
     73 
     74 pub const DeclBlock = struct {
     75     /// Determines whether the `DeclBlock` has been initialized for codegen.
     76     init: bool,
     77     /// Index into the `symbols` list.
     78     symbol_index: u32,
     79     /// Index into the offset table
     80     offset_index: u32,
     81     /// The size of the block and how large part of the data section it occupies.
     82     /// Will be 0 when the Decl will not live inside the data section and `data` will be undefined.
     83     size: u32,
     84     /// Points to the previous and next blocks.
     85     /// Can be used to find the total size, and used to calculate the `offset` based on the previous block.
     86     prev: ?*DeclBlock,
     87     next: ?*DeclBlock,
     88     /// Pointer to data that will be written to the 'data' section.
     89     /// This data either lives in `FnData.code` or is externally managed.
     90     /// For data that does not live inside the 'data' section, this field will be undefined. (size == 0).
     91     data: [*]const u8,
     92 
     93     pub const empty: DeclBlock = .{
     94         .init = false,
     95         .symbol_index = 0,
     96         .offset_index = 0,
     97         .size = 0,
     98         .prev = null,
     99         .next = null,
    100         .data = undefined,
    101     };
    102 
    103     /// Unplugs the `DeclBlock` from the chain
    104     fn unplug(self: *DeclBlock) void {
    105         if (self.prev) |prev| {
    106             prev.next = self.next;
    107         }
    108 
    109         if (self.next) |next| {
    110             next.prev = self.prev;
    111         }
    112         self.next = null;
    113         self.prev = null;
    114     }
    115 };
    116 
    117 pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
    118     assert(options.object_format == .wasm);
    119 
    120     if (build_options.have_llvm and options.use_llvm) {
    121         const self = try createEmpty(allocator, options);
    122         errdefer self.base.destroy();
    123 
    124         self.llvm_object = try LlvmObject.create(allocator, options);
    125         return self;
    126     }
    127 
    128     // TODO: read the file and keep valid parts instead of truncating
    129     const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
    130     errdefer file.close();
    131 
    132     const wasm_bin = try createEmpty(allocator, options);
    133     errdefer wasm_bin.base.destroy();
    134 
    135     wasm_bin.base.file = file;
    136 
    137     try file.writeAll(&(wasm.magic ++ wasm.version));
    138 
    139     return wasm_bin;
    140 }
    141 
    142 pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
    143     const wasm_bin = try gpa.create(Wasm);
    144     wasm_bin.* = .{
    145         .base = .{
    146             .tag = .wasm,
    147             .options = options,
    148             .file = null,
    149             .allocator = gpa,
    150         },
    151     };
    152     return wasm_bin;
    153 }
    154 
    155 pub fn deinit(self: *Wasm) void {
    156     if (build_options.have_llvm) {
    157         if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
    158     }
    159     for (self.symbols.items) |decl| {
    160         decl.fn_link.wasm.functype.deinit(self.base.allocator);
    161         decl.fn_link.wasm.code.deinit(self.base.allocator);
    162         decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
    163     }
    164 
    165     self.funcs.deinit(self.base.allocator);
    166     self.ext_funcs.deinit(self.base.allocator);
    167     self.offset_table.deinit(self.base.allocator);
    168     self.offset_table_free_list.deinit(self.base.allocator);
    169     self.symbols.deinit(self.base.allocator);
    170 }
    171 
    172 pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
    173     if (decl.link.wasm.init) return;
    174 
    175     try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
    176     try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1);
    177 
    178     const block = &decl.link.wasm;
    179     block.init = true;
    180 
    181     block.symbol_index = @intCast(u32, self.symbols.items.len);
    182     self.symbols.appendAssumeCapacity(decl);
    183 
    184     if (self.offset_table_free_list.popOrNull()) |index| {
    185         block.offset_index = index;
    186     } else {
    187         block.offset_index = @intCast(u32, self.offset_table.items.len);
    188         _ = self.offset_table.addOneAssumeCapacity();
    189     }
    190 
    191     self.offset_table.items[block.offset_index] = 0;
    192 
    193     if (decl.ty.zigTypeTag() == .Fn) {
    194         switch (decl.val.tag()) {
    195             // dependent on function type, appends it to the correct list
    196             .function => try self.funcs.append(self.base.allocator, decl),
    197             .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
    198             else => unreachable,
    199         }
    200     }
    201 }
    202 
    203 pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
    204     if (build_options.skip_non_native and builtin.object_format != .wasm) {
    205         @panic("Attempted to compile for object format that was disabled by build configuration");
    206     }
    207     if (build_options.have_llvm) {
    208         if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);
    209     }
    210     const decl = func.owner_decl;
    211     assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
    212 
    213     const fn_data = &decl.fn_link.wasm;
    214     fn_data.functype.items.len = 0;
    215     fn_data.code.items.len = 0;
    216     fn_data.idx_refs.items.len = 0;
    217 
    218     var context = codegen.Context{
    219         .gpa = self.base.allocator,
    220         .air = air,
    221         .liveness = liveness,
    222         .values = .{},
    223         .code = fn_data.code.toManaged(self.base.allocator),
    224         .func_type_data = fn_data.functype.toManaged(self.base.allocator),
    225         .decl = decl,
    226         .err_msg = undefined,
    227         .locals = .{},
    228         .target = self.base.options.target,
    229         .global_error_set = self.base.options.module.?.global_error_set,
    230     };
    231     defer context.deinit();
    232 
    233     // generate the 'code' section for the function declaration
    234     const result = context.genFunc() catch |err| switch (err) {
    235         error.CodegenFail => {
    236             decl.analysis = .codegen_failure;
    237             try module.failed_decls.put(module.gpa, decl, context.err_msg);
    238             return;
    239         },
    240         else => |e| return e,
    241     };
    242     return self.finishUpdateDecl(decl, result, &context);
    243 }
    244 
    245 // Generate code for the Decl, storing it in memory to be later written to
    246 // the file on flush().
    247 pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
    248     if (build_options.skip_non_native and builtin.object_format != .wasm) {
    249         @panic("Attempted to compile for object format that was disabled by build configuration");
    250     }
    251     if (build_options.have_llvm) {
    252         if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);
    253     }
    254     assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
    255 
    256     // TODO don't use this for non-functions
    257     const fn_data = &decl.fn_link.wasm;
    258     fn_data.functype.items.len = 0;
    259     fn_data.code.items.len = 0;
    260     fn_data.idx_refs.items.len = 0;
    261 
    262     var context = codegen.Context{
    263         .gpa = self.base.allocator,
    264         .air = undefined,
    265         .liveness = undefined,
    266         .values = .{},
    267         .code = fn_data.code.toManaged(self.base.allocator),
    268         .func_type_data = fn_data.functype.toManaged(self.base.allocator),
    269         .decl = decl,
    270         .err_msg = undefined,
    271         .locals = .{},
    272         .target = self.base.options.target,
    273         .global_error_set = self.base.options.module.?.global_error_set,
    274     };
    275     defer context.deinit();
    276 
    277     // generate the 'code' section for the function declaration
    278     const result = context.gen(decl.ty, decl.val) catch |err| switch (err) {
    279         error.CodegenFail => {
    280             decl.analysis = .codegen_failure;
    281             try module.failed_decls.put(module.gpa, decl, context.err_msg);
    282             return;
    283         },
    284         else => |e| return e,
    285     };
    286 
    287     return self.finishUpdateDecl(decl, result, &context);
    288 }
    289 
    290 fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: codegen.Result, context: *codegen.Context) !void {
    291     const fn_data: *FnData = &decl.fn_link.wasm;
    292 
    293     fn_data.code = context.code.toUnmanaged();
    294     fn_data.functype = context.func_type_data.toUnmanaged();
    295 
    296     const code: []const u8 = switch (result) {
    297         .appended => @as([]const u8, fn_data.code.items),
    298         .externally_managed => |payload| payload,
    299     };
    300 
    301     const block = &decl.link.wasm;
    302     if (decl.ty.zigTypeTag() == .Fn) {
    303         // as locals are patched afterwards, the offsets of funcidx's are off,
    304         // here we update them to correct them
    305         for (fn_data.idx_refs.items) |*func| {
    306             // For each local, add 6 bytes (count + type)
    307             func.offset += @intCast(u32, context.locals.items.len * 6);
    308         }
    309     } else {
    310         block.size = @intCast(u32, code.len);
    311         block.data = code.ptr;
    312     }
    313 
    314     // If we're updating an existing decl, unplug it first
    315     // to avoid infinite loops due to earlier links
    316     block.unplug();
    317 
    318     if (self.last_block) |last| {
    319         if (last != block) {
    320             last.next = block;
    321             block.prev = last;
    322         }
    323     }
    324     self.last_block = block;
    325 }
    326 
    327 pub fn updateDeclExports(
    328     self: *Wasm,
    329     module: *Module,
    330     decl: *const Module.Decl,
    331     exports: []const *Module.Export,
    332 ) !void {
    333     if (build_options.skip_non_native and builtin.object_format != .wasm) {
    334         @panic("Attempted to compile for object format that was disabled by build configuration");
    335     }
    336     if (build_options.have_llvm) {
    337         if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
    338     }
    339 }
    340 
    341 pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
    342     if (self.getFuncidx(decl)) |func_idx| {
    343         switch (decl.val.tag()) {
    344             .function => _ = self.funcs.swapRemove(func_idx),
    345             .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
    346             else => unreachable,
    347         }
    348     }
    349     const block = &decl.link.wasm;
    350 
    351     if (self.last_block == block) {
    352         self.last_block = block.prev;
    353     }
    354 
    355     block.unplug();
    356 
    357     self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
    358     _ = self.symbols.swapRemove(block.symbol_index);
    359 
    360     // update symbol_index as we swap removed the last symbol into the removed's position
    361     if (block.symbol_index < self.symbols.items.len)
    362         self.symbols.items[block.symbol_index].link.wasm.symbol_index = block.symbol_index;
    363 
    364     block.init = false;
    365 
    366     decl.fn_link.wasm.functype.deinit(self.base.allocator);
    367     decl.fn_link.wasm.code.deinit(self.base.allocator);
    368     decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
    369     decl.fn_link.wasm = undefined;
    370 }
    371 
    372 pub fn flush(self: *Wasm, comp: *Compilation) !void {
    373     if (build_options.have_llvm and self.base.options.use_lld) {
    374         return self.linkWithLLD(comp);
    375     } else {
    376         return self.flushModule(comp);
    377     }
    378 }
    379 
    380 pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
    381     _ = comp;
    382     const tracy = trace(@src());
    383     defer tracy.end();
    384 
    385     const file = self.base.file.?;
    386     const header_size = 5 + 1;
    387     // ptr_width in bytes
    388     const ptr_width = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
    389     // The size of the offset table in bytes
    390     // The table contains all decl's with its corresponding offset into
    391     // the 'data' section
    392     const offset_table_size = @intCast(u32, self.offset_table.items.len * ptr_width);
    393 
    394     // The size of the data, this together with `offset_table_size` amounts to the
    395     // total size of the 'data' section
    396     var first_decl: ?*DeclBlock = null;
    397     const data_size: u32 = if (self.last_block) |last| blk: {
    398         var size = last.size;
    399         var cur = last;
    400         while (cur.prev) |prev| : (cur = prev) {
    401             size += prev.size;
    402         }
    403         first_decl = cur;
    404         break :blk size;
    405     } else 0;
    406 
    407     // No need to rewrite the magic/version header
    408     try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
    409     try file.seekTo(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
    410 
    411     // Type section
    412     {
    413         const header_offset = try reserveVecSectionHeader(file);
    414 
    415         // extern functions are defined in the wasm binary first through the `import`
    416         // section, so define their func types first
    417         for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);
    418         for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);
    419 
    420         try writeVecSectionHeader(
    421             file,
    422             header_offset,
    423             .type,
    424             @intCast(u32, (try file.getPos()) - header_offset - header_size),
    425             @intCast(u32, self.ext_funcs.items.len + self.funcs.items.len),
    426         );
    427     }
    428 
    429     // Import section
    430     {
    431         // TODO: implement non-functions imports
    432         const header_offset = try reserveVecSectionHeader(file);
    433         const writer = file.writer();
    434         for (self.ext_funcs.items) |decl, typeidx| {
    435             try leb.writeULEB128(writer, @intCast(u32, self.host_name.len));
    436             try writer.writeAll(self.host_name);
    437 
    438             // wasm requires the length of the import name with no null-termination
    439             const decl_len = mem.len(decl.name);
    440             try leb.writeULEB128(writer, @intCast(u32, decl_len));
    441             try writer.writeAll(decl.name[0..decl_len]);
    442 
    443             // emit kind and the function type
    444             try writer.writeByte(wasm.externalKind(.function));
    445             try leb.writeULEB128(writer, @intCast(u32, typeidx));
    446         }
    447 
    448         try writeVecSectionHeader(
    449             file,
    450             header_offset,
    451             .import,
    452             @intCast(u32, (try file.getPos()) - header_offset - header_size),
    453             @intCast(u32, self.ext_funcs.items.len),
    454         );
    455     }
    456 
    457     // Function section
    458     {
    459         const header_offset = try reserveVecSectionHeader(file);
    460         const writer = file.writer();
    461         for (self.funcs.items) |_, typeidx| {
    462             const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx);
    463             try leb.writeULEB128(writer, func_idx);
    464         }
    465 
    466         try writeVecSectionHeader(
    467             file,
    468             header_offset,
    469             .function,
    470             @intCast(u32, (try file.getPos()) - header_offset - header_size),
    471             @intCast(u32, self.funcs.items.len),
    472         );
    473     }
    474 
    475     // Memory section
    476     if (data_size != 0) {
    477         const header_offset = try reserveVecSectionHeader(file);
    478         const writer = file.writer();
    479 
    480         try leb.writeULEB128(writer, @as(u32, 0));
    481         // Calculate the amount of memory pages are required and write them.
    482         // Wasm uses 64kB page sizes. Round up to ensure the data segments fit into the memory
    483         try leb.writeULEB128(
    484             writer,
    485             try std.math.divCeil(
    486                 u32,
    487                 offset_table_size + data_size,
    488                 std.wasm.page_size,
    489             ),
    490         );
    491         try writeVecSectionHeader(
    492             file,
    493             header_offset,
    494             .memory,
    495             @intCast(u32, (try file.getPos()) - header_offset - header_size),
    496             @as(u32, 1), // wasm currently only supports 1 linear memory segment
    497         );
    498     }
    499 
    500     // Export section
    501     if (self.base.options.module) |module| {
    502         const header_offset = try reserveVecSectionHeader(file);
    503         const writer = file.writer();
    504         var count: u32 = 0;
    505         for (module.decl_exports.values()) |exports| {
    506             for (exports) |exprt| {
    507                 // Export name length + name
    508                 try leb.writeULEB128(writer, @intCast(u32, exprt.options.name.len));
    509                 try writer.writeAll(exprt.options.name);
    510 
    511                 switch (exprt.exported_decl.ty.zigTypeTag()) {
    512                     .Fn => {
    513                         // Type of the export
    514                         try writer.writeByte(wasm.externalKind(.function));
    515                         // Exported function index
    516                         try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);
    517                     },
    518                     else => return error.TODOImplementNonFnDeclsForWasm,
    519                 }
    520 
    521                 count += 1;
    522             }
    523         }
    524 
    525         // export memory if size is not 0
    526         if (data_size != 0) {
    527             try leb.writeULEB128(writer, @intCast(u32, "memory".len));
    528             try writer.writeAll("memory");
    529             try writer.writeByte(wasm.externalKind(.memory));
    530             try leb.writeULEB128(writer, @as(u32, 0)); // only 1 memory 'object' can exist
    531             count += 1;
    532         }
    533 
    534         try writeVecSectionHeader(
    535             file,
    536             header_offset,
    537             .@"export",
    538             @intCast(u32, (try file.getPos()) - header_offset - header_size),
    539             count,
    540         );
    541     }
    542 
    543     // Code section
    544     {
    545         const header_offset = try reserveVecSectionHeader(file);
    546         const writer = file.writer();
    547         for (self.funcs.items) |decl| {
    548             const fn_data = &decl.fn_link.wasm;
    549 
    550             // Write the already generated code to the file, inserting
    551             // function indexes where required.
    552             var current: u32 = 0;
    553             for (fn_data.idx_refs.items) |idx_ref| {
    554                 try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
    555                 current = idx_ref.offset;
    556                 // Use a fixed width here to make calculating the code size
    557                 // in codegen.wasm.gen() simpler.
    558                 var buf: [5]u8 = undefined;
    559                 leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
    560                 try writer.writeAll(&buf);
    561             }
    562 
    563             try writer.writeAll(fn_data.code.items[current..]);
    564         }
    565         try writeVecSectionHeader(
    566             file,
    567             header_offset,
    568             .code,
    569             @intCast(u32, (try file.getPos()) - header_offset - header_size),
    570             @intCast(u32, self.funcs.items.len),
    571         );
    572     }
    573 
    574     // Data section
    575     if (data_size != 0) {
    576         const header_offset = try reserveVecSectionHeader(file);
    577         const writer = file.writer();
    578         // index to memory section (currently, there can only be 1 memory section in wasm)
    579         try leb.writeULEB128(writer, @as(u32, 0));
    580 
    581         // offset into data section
    582         try writer.writeByte(wasm.opcode(.i32_const));
    583         try leb.writeILEB128(writer, @as(i32, 0));
    584         try writer.writeByte(wasm.opcode(.end));
    585 
    586         const total_size = offset_table_size + data_size;
    587 
    588         // offset table + data size
    589         try leb.writeULEB128(writer, total_size);
    590 
    591         // fill in the offset table and the data segments
    592         const file_offset = try file.getPos();
    593         var cur = first_decl;
    594         var data_offset = offset_table_size;
    595         while (cur) |cur_block| : (cur = cur_block.next) {
    596             if (cur_block.size == 0) continue;
    597             assert(cur_block.init);
    598 
    599             const offset = (cur_block.offset_index) * ptr_width;
    600             var buf: [4]u8 = undefined;
    601             std.mem.writeIntLittle(u32, &buf, data_offset);
    602 
    603             try file.pwriteAll(&buf, file_offset + offset);
    604             try file.pwriteAll(cur_block.data[0..cur_block.size], file_offset + data_offset);
    605             data_offset += cur_block.size;
    606         }
    607 
    608         try file.seekTo(file_offset + data_offset);
    609         try writeVecSectionHeader(
    610             file,
    611             header_offset,
    612             .data,
    613             @intCast(u32, (file_offset + data_offset) - header_offset - header_size),
    614             @intCast(u32, 1), // only 1 data section
    615         );
    616     }
    617 }
    618 
    619 fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
    620     const tracy = trace(@src());
    621     defer tracy.end();
    622 
    623     var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
    624     defer arena_allocator.deinit();
    625     const arena = &arena_allocator.allocator;
    626 
    627     const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
    628 
    629     // If there is no Zig code to compile, then we should skip flushing the output file because it
    630     // will not be part of the linker line anyway.
    631     const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
    632         const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
    633         if (use_stage1) {
    634             const obj_basename = try std.zig.binNameAlloc(arena, .{
    635                 .root_name = self.base.options.root_name,
    636                 .target = self.base.options.target,
    637                 .output_mode = .Obj,
    638             });
    639             const o_directory = module.zig_cache_artifact_directory;
    640             const full_obj_path = try o_directory.join(arena, &[_][]const u8{obj_basename});
    641             break :blk full_obj_path;
    642         }
    643 
    644         try self.flushModule(comp);
    645         const obj_basename = self.base.intermediary_basename.?;
    646         const full_obj_path = try directory.join(arena, &[_][]const u8{obj_basename});
    647         break :blk full_obj_path;
    648     } else null;
    649 
    650     const is_obj = self.base.options.output_mode == .Obj;
    651 
    652     const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt and !is_obj)
    653         comp.compiler_rt_static_lib.?.full_object_path
    654     else
    655         null;
    656 
    657     const target = self.base.options.target;
    658 
    659     const id_symlink_basename = "lld.id";
    660 
    661     var man: Cache.Manifest = undefined;
    662     defer if (!self.base.options.disable_lld_caching) man.deinit();
    663 
    664     var digest: [Cache.hex_digest_len]u8 = undefined;
    665 
    666     if (!self.base.options.disable_lld_caching) {
    667         man = comp.cache_parent.obtain();
    668 
    669         // We are about to obtain this lock, so here we give other processes a chance first.
    670         self.base.releaseLock();
    671 
    672         try man.addListOfFiles(self.base.options.objects);
    673         for (comp.c_object_table.keys()) |key| {
    674             _ = try man.addFile(key.status.success.object_path, null);
    675         }
    676         try man.addOptionalFile(module_obj_path);
    677         try man.addOptionalFile(compiler_rt_path);
    678         man.hash.addOptional(self.base.options.stack_size_override);
    679         man.hash.addListOfBytes(self.base.options.extra_lld_args);
    680 
    681         // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
    682         _ = try man.hit();
    683         digest = man.final();
    684 
    685         var prev_digest_buf: [digest.len]u8 = undefined;
    686         const prev_digest: []u8 = Cache.readSmallFile(
    687             directory.handle,
    688             id_symlink_basename,
    689             &prev_digest_buf,
    690         ) catch |err| blk: {
    691             log.debug("WASM LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
    692             // Handle this as a cache miss.
    693             break :blk prev_digest_buf[0..0];
    694         };
    695         if (mem.eql(u8, prev_digest, &digest)) {
    696             log.debug("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
    697             // Hot diggity dog! The output binary is already there.
    698             self.base.lock = man.toOwnedLock();
    699             return;
    700         }
    701         log.debug("WASM LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
    702 
    703         // We are about to change the output file to be different, so we invalidate the build hash now.
    704         directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
    705             error.FileNotFound => {},
    706             else => |e| return e,
    707         };
    708     }
    709 
    710     const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
    711 
    712     if (self.base.options.output_mode == .Obj) {
    713         // LLD's WASM driver does not support the equvialent of `-r` so we do a simple file copy
    714         // here. TODO: think carefully about how we can avoid this redundant operation when doing
    715         // build-obj. See also the corresponding TODO in linkAsArchive.
    716         const the_object_path = blk: {
    717             if (self.base.options.objects.len != 0)
    718                 break :blk self.base.options.objects[0];
    719 
    720             if (comp.c_object_table.count() != 0)
    721                 break :blk comp.c_object_table.keys()[0].status.success.object_path;
    722 
    723             if (module_obj_path) |p|
    724                 break :blk p;
    725 
    726             // TODO I think this is unreachable. Audit this situation when solving the above TODO
    727             // regarding eliding redundant object -> object transformations.
    728             return error.NoObjectsToLink;
    729         };
    730         // This can happen when using --enable-cache and using the stage1 backend. In this case
    731         // we can skip the file copy.
    732         if (!mem.eql(u8, the_object_path, full_out_path)) {
    733             try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
    734         }
    735     } else {
    736         // Create an LLD command line and invoke it.
    737         var argv = std.ArrayList([]const u8).init(self.base.allocator);
    738         defer argv.deinit();
    739         // We will invoke ourselves as a child process to gain access to LLD.
    740         // This is necessary because LLD does not behave properly as a library -
    741         // it calls exit() and does not reset all global data between invocations.
    742         try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "wasm-ld" });
    743         try argv.append("-error-limit=0");
    744 
    745         if (self.base.options.lto) {
    746             switch (self.base.options.optimize_mode) {
    747                 .Debug => {},
    748                 .ReleaseSmall => try argv.append("-O2"),
    749                 .ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
    750             }
    751         }
    752 
    753         if (self.base.options.output_mode == .Exe) {
    754             // Increase the default stack size to a more reasonable value of 1MB instead of
    755             // the default of 1 Wasm page being 64KB, unless overriden by the user.
    756             try argv.append("-z");
    757             const stack_size = self.base.options.stack_size_override orelse 1048576;
    758             const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
    759             try argv.append(arg);
    760 
    761             // Put stack before globals so that stack overflow results in segfault immediately
    762             // before corrupting globals. See https://github.com/ziglang/zig/issues/4496
    763             try argv.append("--stack-first");
    764 
    765             if (self.base.options.wasi_exec_model == .reactor) {
    766                 // Reactor execution model does not have _start so lld doesn't look for it.
    767                 try argv.append("--no-entry");
    768                 // Make sure "_initialize" is exported even if this is pure Zig WASI reactor
    769                 // where WASM_SYMBOL_EXPORTED flag in LLVM is not set on _initialize.
    770                 try argv.appendSlice(&[_][]const u8{
    771                     "--export",
    772                     "_initialize",
    773                 });
    774             }
    775         } else {
    776             try argv.append("--no-entry"); // So lld doesn't look for _start.
    777             try argv.append("--export-all");
    778         }
    779         try argv.appendSlice(&[_][]const u8{
    780             "--allow-undefined",
    781             "-o",
    782             full_out_path,
    783         });
    784 
    785         if (target.os.tag == .wasi) {
    786             const is_exe_or_dyn_lib = self.base.options.output_mode == .Exe or
    787                 (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic);
    788             if (is_exe_or_dyn_lib) {
    789                 const wasi_emulated_libs = self.base.options.wasi_emulated_libs;
    790                 for (wasi_emulated_libs) |crt_file| {
    791                     try argv.append(try comp.get_libc_crt_file(
    792                         arena,
    793                         wasi_libc.emulatedLibCRFileLibName(crt_file),
    794                     ));
    795                 }
    796 
    797                 if (self.base.options.link_libc) {
    798                     try argv.append(try comp.get_libc_crt_file(
    799                         arena,
    800                         wasi_libc.execModelCrtFileFullName(self.base.options.wasi_exec_model),
    801                     ));
    802                     try argv.append(try comp.get_libc_crt_file(arena, "libc.a"));
    803                 }
    804 
    805                 if (self.base.options.link_libcpp) {
    806                     try argv.append(comp.libcxx_static_lib.?.full_object_path);
    807                     try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
    808                 }
    809             }
    810         }
    811 
    812         // Positional arguments to the linker such as object files.
    813         try argv.appendSlice(self.base.options.objects);
    814 
    815         for (comp.c_object_table.keys()) |key| {
    816             try argv.append(key.status.success.object_path);
    817         }
    818         if (module_obj_path) |p| {
    819             try argv.append(p);
    820         }
    821 
    822         if (self.base.options.output_mode != .Obj and
    823             !self.base.options.skip_linker_dependencies and
    824             !self.base.options.link_libc)
    825         {
    826             try argv.append(comp.libc_static_lib.?.full_object_path);
    827         }
    828 
    829         if (compiler_rt_path) |p| {
    830             try argv.append(p);
    831         }
    832 
    833         if (self.base.options.verbose_link) {
    834             // Skip over our own name so that the LLD linker name is the first argv item.
    835             Compilation.dump_argv(argv.items[1..]);
    836         }
    837 
    838         // Sadly, we must run LLD as a child process because it does not behave
    839         // properly as a library.
    840         const child = try std.ChildProcess.init(argv.items, arena);
    841         defer child.deinit();
    842 
    843         if (comp.clang_passthrough_mode) {
    844             child.stdin_behavior = .Inherit;
    845             child.stdout_behavior = .Inherit;
    846             child.stderr_behavior = .Inherit;
    847 
    848             const term = child.spawnAndWait() catch |err| {
    849                 log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
    850                 return error.UnableToSpawnSelf;
    851             };
    852             switch (term) {
    853                 .Exited => |code| {
    854                     if (code != 0) {
    855                         // TODO https://github.com/ziglang/zig/issues/6342
    856                         std.process.exit(1);
    857                     }
    858                 },
    859                 else => std.process.abort(),
    860             }
    861         } else {
    862             child.stdin_behavior = .Ignore;
    863             child.stdout_behavior = .Ignore;
    864             child.stderr_behavior = .Pipe;
    865 
    866             try child.spawn();
    867 
    868             const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
    869 
    870             const term = child.wait() catch |err| {
    871                 log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
    872                 return error.UnableToSpawnSelf;
    873             };
    874 
    875             switch (term) {
    876                 .Exited => |code| {
    877                     if (code != 0) {
    878                         // TODO parse this output and surface with the Compilation API rather than
    879                         // directly outputting to stderr here.
    880                         std.debug.print("{s}", .{stderr});
    881                         return error.LLDReportedFailure;
    882                     }
    883                 },
    884                 else => {
    885                     log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
    886                     return error.LLDCrashed;
    887                 },
    888             }
    889 
    890             if (stderr.len != 0) {
    891                 log.warn("unexpected LLD stderr:\n{s}", .{stderr});
    892             }
    893         }
    894     }
    895 
    896     if (!self.base.options.disable_lld_caching) {
    897         // Update the file with the digest. If it fails we can continue; it only
    898         // means that the next invocation will have an unnecessary cache miss.
    899         Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
    900             log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)});
    901         };
    902         // Again failure here only means an unnecessary cache miss.
    903         man.writeManifest() catch |err| {
    904             log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
    905         };
    906         // We hang on to this lock so that the output file path can be used without
    907         // other processes clobbering it.
    908         self.base.lock = man.toOwnedLock();
    909     }
    910 }
    911 
    912 /// Get the current index of a given Decl in the function list
    913 /// This will correctly provide the index, regardless whether the function is extern or not
    914 /// TODO: we could maintain a hash map to potentially make this simpler
    915 fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {
    916     var offset: u32 = 0;
    917     const slice = switch (decl.val.tag()) {
    918         .function => blk: {
    919             // when the target is a regular function, we have to calculate
    920             // the offset of where the index starts
    921             offset += self.getFuncIdxOffset();
    922             break :blk self.funcs.items;
    923         },
    924         .extern_fn => self.ext_funcs.items,
    925         else => return null,
    926     };
    927     return for (slice) |func, idx| {
    928         if (func == decl) break @intCast(u32, offset + idx);
    929     } else null;
    930 }
    931 
    932 /// Based on the size of `ext_funcs` returns the
    933 /// offset of the function indices
    934 fn getFuncIdxOffset(self: Wasm) u32 {
    935     return @intCast(u32, self.ext_funcs.items.len);
    936 }
    937 
    938 fn reserveVecSectionHeader(file: fs.File) !u64 {
    939     // section id + fixed leb contents size + fixed leb vector length
    940     const header_size = 1 + 5 + 5;
    941     // TODO: this should be a single lseek(2) call, but fs.File does not
    942     // currently provide a way to do this.
    943     try file.seekBy(header_size);
    944     return (try file.getPos()) - header_size;
    945 }
    946 
    947 fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size: u32, items: u32) !void {
    948     var buf: [1 + 5 + 5]u8 = undefined;
    949     buf[0] = @enumToInt(section);
    950     leb.writeUnsignedFixed(5, buf[1..6], size);
    951     leb.writeUnsignedFixed(5, buf[6..], items);
    952     try file.pwriteAll(&buf, offset);
    953 }