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 }