| ... | ... | @@ -29,6 +29,7 @@ const Air = @import("../Air.zig"); |
| 29 | 29 | const Liveness = @import("../Liveness.zig"); |
| 30 | 30 | const Symbol = @import("Wasm/Symbol.zig"); |
| 31 | 31 | const Object = @import("Wasm/Object.zig"); |
| 32 | const Archive = @import("Wasm/Archive.zig"); |
| 32 | 33 | const types = @import("Wasm/types.zig"); |
| 33 | 34 | |
| 34 | 35 | pub const base_tag = link.File.Tag.wasm; |
| ... | ... | @@ -125,6 +126,10 @@ function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{}, |
| 125 | 126 | |
| 126 | 127 | /// All object files and their data which are linked into the final binary |
| 127 | 128 | objects: std.ArrayListUnmanaged(Object) = .{}, |
| 129 | /// All archive files that are lazy loaded. |
| 130 | /// e.g. when an undefined symbol references a symbol from the archive. |
| 131 | archives: std.ArrayListUnmanaged(Archive) = .{}, |
| 132 | |
| 128 | 133 | /// A map of global names (read: offset into string table) to their symbol location |
| 129 | 134 | globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .{}, |
| 130 | 135 | /// Maps discarded symbols and their positions to the location of the symbol |
| ... | ... | @@ -133,6 +138,8 @@ discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{}, |
| 133 | 138 | /// List of all symbol locations which have been resolved by the linker and will be emit |
| 134 | 139 | /// into the final binary. |
| 135 | 140 | resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{}, |
| 141 | /// Symbols that remain undefined after symbol resolution. |
| 142 | undefs: std.StringArrayHashMapUnmanaged(SymbolLoc) = .{}, |
| 136 | 143 | /// Maps a symbol's location to an atom. This can be used to find meta |
| 137 | 144 | /// data of a symbol, such as its size, or its offset to perform a relocation. |
| 138 | 145 | /// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped. |
| ... | ... | @@ -359,6 +366,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm { |
| 359 | 366 | fn parseInputFiles(self: *Wasm, files: []const []const u8) !void { |
| 360 | 367 | for (files) |path| { |
| 361 | 368 | if (try self.parseObjectFile(path)) continue; |
| 369 | if (try self.parseArchive(path, false)) continue; // load archives lazily |
| 362 | 370 | log.warn("Unexpected file format at path: '{s}'", .{path}); |
| 363 | 371 | } |
| 364 | 372 | } |
| ... | ... | @@ -371,10 +379,7 @@ fn parseObjectFile(self: *Wasm, path: []const u8) !bool { |
| 371 | 379 | errdefer file.close(); |
| 372 | 380 | |
| 373 | 381 | var object = Object.create(self.base.allocator, file, path) catch |err| switch (err) { |
| 374 | | error.InvalidMagicByte, error.NotObjectFile => { |
| 375 | | log.warn("Self hosted linker does not support non-object file parsing: {s}", .{@errorName(err)}); |
| 376 | | return false; |
| 377 | | }, |
| 382 | error.InvalidMagicByte, error.NotObjectFile => return false, |
| 378 | 383 | else => |e| return e, |
| 379 | 384 | }; |
| 380 | 385 | errdefer object.deinit(self.base.allocator); |
| ... | ... | @@ -382,6 +387,56 @@ fn parseObjectFile(self: *Wasm, path: []const u8) !bool { |
| 382 | 387 | return true; |
| 383 | 388 | } |
| 384 | 389 | |
| 390 | /// Parses an archive file and will then parse each object file |
| 391 | /// that was found in the archive file. |
| 392 | /// Returns false when the file is not an archive file. |
| 393 | /// May return an error instead when parsing failed. |
| 394 | /// |
| 395 | /// When `force_load` is `true`, it will for link all object files in the archive. |
| 396 | /// When false, it will only link with object files that contain symbols that |
| 397 | /// are referenced by other object files or Zig code. |
| 398 | fn parseArchive(self: *Wasm, path: []const u8, force_load: bool) !bool { |
| 399 | const file = try fs.cwd().openFile(path, .{}); |
| 400 | errdefer file.close(); |
| 401 | |
| 402 | var archive: Archive = .{ |
| 403 | .file = file, |
| 404 | .name = path, |
| 405 | }; |
| 406 | archive.parse(self.base.allocator) catch |err| switch (err) { |
| 407 | error.EndOfStream, error.NotArchive => { |
| 408 | archive.deinit(self.base.allocator); |
| 409 | return false; |
| 410 | }, |
| 411 | else => |e| return e, |
| 412 | }; |
| 413 | |
| 414 | if (!force_load) { |
| 415 | errdefer archive.deinit(self.base.allocator); |
| 416 | try self.archives.append(self.base.allocator, archive); |
| 417 | return true; |
| 418 | } |
| 419 | defer archive.deinit(self.base.allocator); |
| 420 | |
| 421 | // In this case we must force link all embedded object files within the archive |
| 422 | // We loop over all symbols, and then group them by offset as the offset |
| 423 | // notates where the object file starts. |
| 424 | var offsets = std.AutoArrayHashMap(u32, void).init(self.base.allocator); |
| 425 | defer offsets.deinit(); |
| 426 | for (archive.toc.values()) |symbol_offsets| { |
| 427 | for (symbol_offsets.items) |sym_offset| { |
| 428 | try offsets.put(sym_offset, {}); |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | for (offsets.keys()) |file_offset| { |
| 433 | const object = try self.objects.addOne(self.base.allocator); |
| 434 | object.* = try archive.parseObject(self.base.allocator, file_offset); |
| 435 | } |
| 436 | |
| 437 | return true; |
| 438 | } |
| 439 | |
| 385 | 440 | fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { |
| 386 | 441 | const object: Object = self.objects.items[object_index]; |
| 387 | 442 | log.debug("Resolving symbols in object: '{s}'", .{object.name}); |
| ... | ... | @@ -414,6 +469,10 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { |
| 414 | 469 | if (!maybe_existing.found_existing) { |
| 415 | 470 | maybe_existing.value_ptr.* = location; |
| 416 | 471 | try self.resolved_symbols.putNoClobber(self.base.allocator, location, {}); |
| 472 | |
| 473 | if (symbol.isUndefined()) { |
| 474 | try self.undefs.putNoClobber(self.base.allocator, sym_name, location); |
| 475 | } |
| 417 | 476 | continue; |
| 418 | 477 | } |
| 419 | 478 | |
| ... | ... | @@ -456,6 +515,42 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { |
| 456 | 515 | try self.globals.put(self.base.allocator, sym_name_index, location); |
| 457 | 516 | try self.resolved_symbols.put(self.base.allocator, location, {}); |
| 458 | 517 | assert(self.resolved_symbols.swapRemove(existing_loc)); |
| 518 | if (existing_sym.isUndefined()) { |
| 519 | // ensure order remains intact in case we later |
| 520 | // resolve symbols again in a loop |
| 521 | assert(self.undefs.orderedRemove(sym_name)); |
| 522 | } |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | fn resolveSymbolsInArchives(self: *Wasm) !void { |
| 527 | if (self.archives.items.len == 0) return; |
| 528 | |
| 529 | log.debug("Resolving symbols in archives", .{}); |
| 530 | var index: u32 = 0; |
| 531 | undef_loop: while (index < self.undefs.count()) { |
| 532 | const undef_sym_loc = self.undefs.values()[index]; |
| 533 | const sym_name = undef_sym_loc.getName(self); |
| 534 | |
| 535 | for (self.archives.items) |archive| { |
| 536 | const offset = archive.toc.get(sym_name) orelse { |
| 537 | // symbol does not exist in this archive |
| 538 | continue; |
| 539 | }; |
| 540 | |
| 541 | // Symbol is found in unparsed object file within current archive. |
| 542 | // Parse object and and resolve symbols again before we check remaining |
| 543 | // undefined symbols. |
| 544 | const object_file_index = @intCast(u16, self.objects.items.len); |
| 545 | const object = try self.objects.addOne(self.base.allocator); |
| 546 | object.* = try archive.parseObject(self.base.allocator, offset.items[0]); |
| 547 | try self.resolveSymbolsInObject(object_file_index); |
| 548 | |
| 549 | // continue loop for any remaining undefined symbols that still exist |
| 550 | // after resolving last object file |
| 551 | continue :undef_loop; |
| 552 | } |
| 553 | index += 1; |
| 459 | 554 | } |
| 460 | 555 | } |
| 461 | 556 | |
| ... | ... | @@ -789,6 +884,7 @@ pub fn getGlobalSymbol(self: *Wasm, name: []const u8) !u32 { |
| 789 | 884 | self.symbols.items[sym_index] = symbol; |
| 790 | 885 | gop.value_ptr.* = .{ .index = sym_index, .file = null }; |
| 791 | 886 | try self.resolved_symbols.put(self.base.allocator, gop.value_ptr.*, {}); |
| 887 | try self.undefs.putNoClobber(self.base.allocator, name, gop.value_ptr.*); |
| 792 | 888 | return sym_index; |
| 793 | 889 | } |
| 794 | 890 | |
| ... | ... | @@ -1017,6 +1113,7 @@ pub fn addOrUpdateImport( |
| 1017 | 1113 | const loc: SymbolLoc = .{ .file = null, .index = symbol_index }; |
| 1018 | 1114 | global_gop.value_ptr.* = loc; |
| 1019 | 1115 | try self.resolved_symbols.put(self.base.allocator, loc, {}); |
| 1116 | try self.undefs.putNoClobber(self.base.allocator, name, loc); |
| 1020 | 1117 | } |
| 1021 | 1118 | |
| 1022 | 1119 | if (type_index) |ty_index| { |
| ... | ... | @@ -1298,7 +1395,7 @@ fn mergeTypes(self: *Wasm) !void { |
| 1298 | 1395 | // type inserted. If we do this for the same function multiple times, |
| 1299 | 1396 | // it will be overwritten with the incorrect type. |
| 1300 | 1397 | var dirty = std.AutoHashMap(u32, void).init(self.base.allocator); |
| 1301 | | try dirty.ensureUnusedCapacity(@intCast(u32, self.functions.count()) + self.imported_functions_count); |
| 1398 | try dirty.ensureUnusedCapacity(@intCast(u32, self.functions.count())); |
| 1302 | 1399 | defer dirty.deinit(); |
| 1303 | 1400 | |
| 1304 | 1401 | for (self.resolved_symbols.keys()) |sym_loc| { |
| ... | ... | @@ -1313,22 +1410,17 @@ fn mergeTypes(self: *Wasm) !void { |
| 1313 | 1410 | continue; |
| 1314 | 1411 | } |
| 1315 | 1412 | |
| 1316 | | if (dirty.contains(symbol.index)) { |
| 1317 | | continue; // We already added the type of this symbol |
| 1318 | | } |
| 1319 | | |
| 1320 | 1413 | if (symbol.isUndefined()) { |
| 1321 | 1414 | log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)}); |
| 1322 | 1415 | const import: *types.Import = self.imports.getPtr(sym_loc).?; |
| 1323 | 1416 | const original_type = object.func_types[import.kind.function]; |
| 1324 | 1417 | import.kind.function = try self.putOrGetFuncType(original_type); |
| 1325 | | } else { |
| 1418 | } else if (!dirty.contains(symbol.index)) { |
| 1326 | 1419 | log.debug("Adding type from function '{s}'", .{sym_loc.getName(self)}); |
| 1327 | 1420 | const func = &self.functions.values()[symbol.index - self.imported_functions_count]; |
| 1328 | 1421 | func.type_index = try self.putOrGetFuncType(object.func_types[func.type_index]); |
| 1422 | dirty.putAssumeCapacityNoClobber(symbol.index, {}); |
| 1329 | 1423 | } |
| 1330 | | |
| 1331 | | dirty.putAssumeCapacityNoClobber(symbol.index, {}); |
| 1332 | 1424 | } |
| 1333 | 1425 | log.debug("Completed merging and deduplicating types. Total count: ({d})", .{self.func_types.items.len}); |
| 1334 | 1426 | } |
| ... | ... | @@ -1747,6 +1839,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod |
| 1747 | 1839 | while (object_index < self.objects.items.len) : (object_index += 1) { |
| 1748 | 1840 | try self.resolveSymbolsInObject(object_index); |
| 1749 | 1841 | } |
| 1842 | try self.resolveSymbolsInArchives(); |
| 1750 | 1843 | |
| 1751 | 1844 | // When we finish/error we reset the state of the linker |
| 1752 | 1845 | // So we can rebuild the binary file on each incremental update |