diff --git a/src/link.zig b/src/link.zig index ef0c2fa269..7f53bde320 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1085,7 +1085,7 @@ pub const File = struct { const use_lld = build_options.have_llvm and base.comp.config.use_lld; if (use_lld) return; switch (base.tag) { - inline .elf => |tag| { + inline .elf, .wasm => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input); }, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 8a5e8f9282..ccc9e24bf6 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -823,9 +823,8 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod const sub_prog_node = prog_node.start("ELF Flush", 0); defer sub_prog_node.end(); - const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type. const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{ - .root_dir = directory, + .root_dir = self.base.emit.root_dir, .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname| try fs.path.join(arena, &.{ dirname, path }) else @@ -1104,7 +1103,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void { pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void { const diags = &self.base.comp.link_diags; const obj = link.openObject(path, false, false) catch |err| { - switch (diags.failParse(path, "failed to open object {}: {s}", .{ path, @errorName(err) })) { + switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) { error.LinkFailure => return, } }; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 358436ff9c..b2780a9f4e 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -156,7 +156,7 @@ function_table: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .empty, /// All archive files that are lazy loaded. /// e.g. when an undefined symbol references a symbol from the archive. -archives: std.ArrayListUnmanaged(Archive) = .empty, +lazy_archives: std.ArrayListUnmanaged(LazyArchive) = .empty, /// A map of global names (read: offset into string table) to their symbol location globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .empty, @@ -176,6 +176,10 @@ undefs: std.AutoArrayHashMapUnmanaged(u32, SymbolLoc) = .empty, /// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped. symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, Atom.Index) = .empty, +/// `--verbose-link` output. +/// Initialized on creation, appended to as inputs are added, printed during `flush`. +dump_argv_list: std.ArrayListUnmanaged([]const u8), + /// Index into objects array or the zig object. pub const ObjectId = enum(u16) { zig_object = std.math.maxInt(u16) - 1, @@ -200,6 +204,18 @@ pub const OptionalObjectId = enum(u16) { } }; +const LazyArchive = struct { + path: Path, + file_contents: []const u8, + archive: Archive, + + fn deinit(la: *LazyArchive, gpa: Allocator) void { + gpa.free(la.path.sub_path); + gpa.free(la.file_contents); + la.* = undefined; + } +}; + pub const Segment = struct { alignment: Alignment, size: u32, @@ -450,6 +466,7 @@ pub fn createEmpty( .named => |name| name, }, .zig_object = null, + .dump_argv_list = .empty, }; if (use_llvm and comp.config.have_zcu) { wasm.llvm_object = try LlvmObject.create(arena, comp); @@ -596,7 +613,10 @@ pub fn createEmpty( const zig_object = try arena.create(ZigObject); wasm.zig_object = zig_object; zig_object.* = .{ - .path = try std.fmt.allocPrint(gpa, "{s}.o", .{std.fs.path.stem(zcu.main_mod.root_src_path)}), + .path = .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = try std.fmt.allocPrint(gpa, "{s}.o", .{fs.path.stem(zcu.main_mod.root_src_path)}), + }, .stack_pointer_sym = .null, }; try zig_object.init(wasm); @@ -657,28 +677,34 @@ fn createSyntheticSymbolOffset(wasm: *Wasm, name_offset: u32, tag: Symbol.Tag) ! return loc; } -/// Parses the object file from given path. Returns true when the given file was an object -/// file and parsed successfully. Returns false when file is not an object file. -/// May return an error instead when parsing failed. -fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool { +fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void { const diags = &wasm.base.comp.link_diags; - - const obj_file = try fs.cwd().openFile(path, .{}); - errdefer obj_file.close(); - - const gpa = wasm.base.comp.gpa; - var object = Object.create(wasm, obj_file, path, null) catch |err| switch (err) { - error.InvalidMagicByte, error.NotObjectFile => return false, - else => |e| { - var err_note = try diags.addErrorWithNotes(1); - try err_note.addMsg("Failed parsing object file: {s}", .{@errorName(e)}); - try err_note.addNote("while parsing '{s}'", .{path}); - return error.FlushFailure; - }, + const obj = link.openObject(path, false, false) catch |err| { + switch (diags.failParse(path, "failed to open object: {s}", .{@errorName(err)})) { + error.LinkFailure => return, + } }; - errdefer object.deinit(gpa); - try wasm.objects.append(gpa, object); - return true; + wasm.parseObject(obj) catch |err| { + switch (diags.failParse(path, "failed to parse object: {s}", .{@errorName(err)})) { + error.LinkFailure => return, + } + }; +} + +fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void { + defer obj.file.close(); + const gpa = wasm.base.comp.gpa; + try wasm.objects.ensureUnusedCapacity(gpa, 1); + const stat = try obj.file.stat(); + const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; + + const file_contents = try gpa.alloc(u8, size); + defer gpa.free(file_contents); + + const n = try obj.file.preadAll(file_contents, 0); + if (n != file_contents.len) return error.UnexpectedEndOfFile; + + wasm.objects.appendAssumeCapacity(try Object.create(wasm, file_contents, obj.path, null)); } /// Creates a new empty `Atom` and returns its `Atom.Index` @@ -703,43 +729,37 @@ pub fn getAtomPtr(wasm: *Wasm, index: Atom.Index) *Atom { return &wasm.managed_atoms.items[@intFromEnum(index)]; } -/// Parses an archive file and will then parse each object file -/// that was found in the archive file. -/// Returns false when the file is not an archive file. -/// May return an error instead when parsing failed. -/// -/// When `force_load` is `true`, it will for link all object files in the archive. -/// When false, it will only link with object files that contain symbols that -/// are referenced by other object files or Zig code. -fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { +fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void { const gpa = wasm.base.comp.gpa; - const diags = &wasm.base.comp.link_diags; - const archive_file = try fs.cwd().openFile(path, .{}); - errdefer archive_file.close(); + defer obj.file.close(); - var archive: Archive = .{ - .file = archive_file, - .name = path, - }; - archive.parse(gpa) catch |err| switch (err) { - error.EndOfStream, error.NotArchive => { - archive.deinit(gpa); - return false; - }, - else => |e| { - var err_note = try diags.addErrorWithNotes(1); - try err_note.addMsg("Failed parsing archive: {s}", .{@errorName(e)}); - try err_note.addNote("while parsing archive {s}", .{path}); - return error.FlushFailure; - }, - }; + const stat = try obj.file.stat(); + const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; - if (!force_load) { + const file_contents = try gpa.alloc(u8, size); + var keep_file_contents = false; + defer if (!keep_file_contents) gpa.free(file_contents); + + const n = try obj.file.preadAll(file_contents, 0); + if (n != file_contents.len) return error.UnexpectedEndOfFile; + + var archive = try Archive.parse(gpa, file_contents); + + if (!obj.must_link) { errdefer archive.deinit(gpa); - try wasm.archives.append(gpa, archive); - return true; + try wasm.lazy_archives.append(gpa, .{ + .path = .{ + .root_dir = obj.path.root_dir, + .sub_path = try gpa.dupe(u8, obj.path.sub_path), + }, + .file_contents = file_contents, + .archive = archive, + }); + keep_file_contents = true; + return; } + defer archive.deinit(gpa); // In this case we must force link all embedded object files within the archive @@ -754,16 +774,9 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { } for (offsets.keys()) |file_offset| { - const object = archive.parseObject(wasm, file_offset) catch |e| { - var err_note = try diags.addErrorWithNotes(1); - try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)}); - try err_note.addNote("while parsing object in archive {s}", .{path}); - return error.FlushFailure; - }; + const object = try archive.parseObject(wasm, file_contents[file_offset..], obj.path); try wasm.objects.append(gpa, object); } - - return true; } fn requiresTLSReloc(wasm: *const Wasm) bool { @@ -775,7 +788,7 @@ fn requiresTLSReloc(wasm: *const Wasm) bool { return false; } -fn objectPath(wasm: *const Wasm, object_id: ObjectId) []const u8 { +fn objectPath(wasm: *const Wasm, object_id: ObjectId) Path { const obj = wasm.objectById(object_id) orelse return wasm.zig_object.?.path; return obj.path; } @@ -854,7 +867,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { const gpa = wasm.base.comp.gpa; const diags = &wasm.base.comp.link_diags; const obj_path = objectPath(wasm, object_id); - log.debug("Resolving symbols in object: '{s}'", .{obj_path}); + log.debug("Resolving symbols in object: '{'}'", .{obj_path}); const symbols = objectSymbols(wasm, object_id); for (symbols, 0..) |symbol, i| { @@ -871,9 +884,7 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { if (symbol.isLocal()) { if (symbol.isUndefined()) { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("Local symbols are not allowed to reference imports", .{}); - try err.addNote("symbol '{s}' defined in '{s}'", .{ sym_name, obj_path }); + diags.addParseError(obj_path, "local symbol '{s}' references import", .{sym_name}); } try wasm.resolved_symbols.putNoClobber(gpa, location, {}); continue; @@ -892,7 +903,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { const existing_loc = maybe_existing.value_ptr.*; const existing_sym: *Symbol = wasm.symbolLocSymbol(existing_loc); - const existing_file_path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else wasm.name; + const existing_file_path: Path = if (existing_loc.file.unwrap()) |id| objectPath(wasm, id) else .{ + .root_dir = std.Build.Cache.Directory.cwd(), + .sub_path = wasm.name, + }; if (!existing_sym.isUndefined()) outer: { if (!symbol.isUndefined()) inner: { @@ -905,8 +919,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { // both are defined and weak, we have a symbol collision. var err = try diags.addErrorWithNotes(2); try err.addMsg("symbol '{s}' defined multiple times", .{sym_name}); - try err.addNote("first definition in '{s}'", .{existing_file_path}); - try err.addNote("next definition in '{s}'", .{obj_path}); + try err.addNote("first definition in '{'}'", .{existing_file_path}); + try err.addNote("next definition in '{'}'", .{obj_path}); } try wasm.discarded.put(gpa, location, existing_loc); @@ -916,8 +930,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { if (symbol.tag != existing_sym.tag) { var err = try diags.addErrorWithNotes(2); try err.addMsg("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); - try err.addNote("first definition in '{s}'", .{existing_file_path}); - try err.addNote("next definition in '{s}'", .{obj_path}); + try err.addNote("first definition in '{'}'", .{existing_file_path}); + try err.addNote("next definition in '{'}'", .{obj_path}); } if (existing_sym.isUndefined() and symbol.isUndefined()) { @@ -940,8 +954,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { existing_name, module_name, }); - try err.addNote("first definition in '{s}'", .{existing_file_path}); - try err.addNote("next definition in '{s}'", .{obj_path}); + try err.addNote("first definition in '{'}'", .{existing_file_path}); + try err.addNote("next definition in '{'}'", .{obj_path}); } } @@ -956,8 +970,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { var err = try diags.addErrorWithNotes(2); try err.addMsg("symbol '{s}' mismatching global types", .{sym_name}); - try err.addNote("first definition in '{s}'", .{existing_file_path}); - try err.addNote("next definition in '{s}'", .{obj_path}); + try err.addNote("first definition in '{'}'", .{existing_file_path}); + try err.addNote("next definition in '{'}'", .{obj_path}); } } @@ -968,8 +982,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { var err = try diags.addErrorWithNotes(3); try err.addMsg("symbol '{s}' mismatching function signatures.", .{sym_name}); try err.addNote("expected signature {}, but found signature {}", .{ existing_ty, new_ty }); - try err.addNote("first definition in '{s}'", .{existing_file_path}); - try err.addNote("next definition in '{s}'", .{obj_path}); + try err.addNote("first definition in '{'}'", .{existing_file_path}); + try err.addNote("next definition in '{'}'", .{obj_path}); } } @@ -983,8 +997,8 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { // simply overwrite with the new symbol log.debug("Overwriting symbol '{s}'", .{sym_name}); - log.debug(" old definition in '{s}'", .{existing_file_path}); - log.debug(" new definition in '{s}'", .{obj_path}); + log.debug(" old definition in '{'}'", .{existing_file_path}); + log.debug(" new definition in '{'}'", .{obj_path}); try wasm.discarded.putNoClobber(gpa, existing_loc, location); maybe_existing.value_ptr.* = location; try wasm.globals.put(gpa, sym_name_index, location); @@ -997,31 +1011,29 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_id: ObjectId) !void { } fn resolveSymbolsInArchives(wasm: *Wasm) !void { + if (wasm.lazy_archives.items.len == 0) return; const gpa = wasm.base.comp.gpa; const diags = &wasm.base.comp.link_diags; - if (wasm.archives.items.len == 0) return; - log.debug("Resolving symbols in archives", .{}); + log.debug("Resolving symbols in lazy_archives", .{}); var index: u32 = 0; undef_loop: while (index < wasm.undefs.count()) { const sym_name_index = wasm.undefs.keys()[index]; - for (wasm.archives.items) |archive| { + for (wasm.lazy_archives.items) |lazy_archive| { const sym_name = wasm.string_table.get(sym_name_index); - log.debug("Detected symbol '{s}' in archive '{s}', parsing objects..", .{ sym_name, archive.name }); - const offset = archive.toc.get(sym_name) orelse { - // symbol does not exist in this archive - continue; - }; + log.debug("Detected symbol '{s}' in archive '{'}', parsing objects..", .{ + sym_name, lazy_archive.path, + }); + const offset = lazy_archive.archive.toc.get(sym_name) orelse continue; // symbol does not exist in this archive // Symbol is found in unparsed object file within current archive. // Parse object and and resolve symbols again before we check remaining // undefined symbols. - const object = archive.parseObject(wasm, offset.items[0]) catch |e| { - var err_note = try diags.addErrorWithNotes(1); - try err_note.addMsg("Failed parsing object: {s}", .{@errorName(e)}); - try err_note.addNote("while parsing object in archive {s}", .{archive.name}); - return error.FlushFailure; + const file_contents = lazy_archive.file_contents[offset.items[0]..]; + const object = lazy_archive.archive.parseObject(wasm, file_contents, lazy_archive.path) catch |err| { + // TODO this fails to include information to identify which object failed + return diags.failParse(lazy_archive.path, "failed to parse object in archive: {s}", .{@errorName(err)}); }; try wasm.objects.append(gpa, object); try wasm.resolveSymbolsInObject(@enumFromInt(wasm.objects.items.len - 1)); @@ -1323,9 +1335,11 @@ fn validateFeatures( allowed[used_index] = is_enabled; emit_features_count.* += @intFromBool(is_enabled); } else if (is_enabled and !allowed[used_index]) { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("feature '{}' not allowed, but used by linked object", .{@as(Feature.Tag, @enumFromInt(used_index))}); - try err.addNote("defined in '{s}'", .{wasm.objects.items[used_set >> 1].path}); + diags.addParseError( + wasm.objects.items[used_set >> 1].path, + "feature '{}' not allowed, but used by linked object", + .{@as(Feature.Tag, @enumFromInt(used_index))}, + ); valid_feature_set = false; } } @@ -1337,10 +1351,10 @@ fn validateFeatures( if (shared_memory) { const disallowed_feature = disallowed[@intFromEnum(Feature.Tag.shared_mem)]; if (@as(u1, @truncate(disallowed_feature)) != 0) { - var err = try diags.addErrorWithNotes(0); - try err.addMsg( - "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled", - .{wasm.objects.items[disallowed_feature >> 1].path}, + diags.addParseError( + wasm.objects.items[disallowed_feature >> 1].path, + "shared-memory is disallowed because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled", + .{}, ); valid_feature_set = false; } @@ -1371,8 +1385,8 @@ fn validateFeatures( if (@as(u1, @truncate(disallowed_feature)) != 0) { var err = try diags.addErrorWithNotes(2); try err.addMsg("feature '{}' is disallowed, but used by linked object", .{feature.tag}); - try err.addNote("disallowed by '{s}'", .{wasm.objects.items[disallowed_feature >> 1].path}); - try err.addNote("used in '{s}'", .{object.path}); + try err.addNote("disallowed by '{'}'", .{wasm.objects.items[disallowed_feature >> 1].path}); + try err.addNote("used in '{'}'", .{object.path}); valid_feature_set = false; } @@ -1385,8 +1399,8 @@ fn validateFeatures( if (is_required and !object_used_features[feature_index]) { var err = try diags.addErrorWithNotes(2); try err.addMsg("feature '{}' is required but not used in linked object", .{@as(Feature.Tag, @enumFromInt(feature_index))}); - try err.addNote("required by '{s}'", .{wasm.objects.items[required_feature >> 1].path}); - try err.addNote("missing in '{s}'", .{object.path}); + try err.addNote("required by '{'}'", .{wasm.objects.items[required_feature >> 1].path}); + try err.addNote("missing in '{'}'", .{object.path}); valid_feature_set = false; } } @@ -1460,19 +1474,25 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void { const symbol = wasm.symbolLocSymbol(undef); if (symbol.tag == .data) { found_undefined_symbols = true; - const file_name = switch (undef.file) { - .zig_object => wasm.zig_object.?.path, - .none => wasm.name, - _ => wasm.objects.items[@intFromEnum(undef.file)].path, - }; const symbol_name = wasm.symbolLocName(undef); - var err = try diags.addErrorWithNotes(1); - try err.addMsg("could not resolve undefined symbol '{s}'", .{symbol_name}); - try err.addNote("defined in '{s}'", .{file_name}); + switch (undef.file) { + .zig_object => { + // TODO: instead of saying the zig compilation unit, attach an actual source location + // to this diagnostic + diags.addError("unresolved symbol in Zig compilation unit: {s}", .{symbol_name}); + }, + .none => { + diags.addError("internal linker bug: unresolved synthetic symbol: {s}", .{symbol_name}); + }, + _ => { + const path = wasm.objects.items[@intFromEnum(undef.file)].path; + diags.addParseError(path, "unresolved symbol: {s}", .{symbol_name}); + }, + } } } if (found_undefined_symbols) { - return error.FlushFailure; + return error.LinkFailure; } } @@ -1493,9 +1513,8 @@ pub fn deinit(wasm: *Wasm) void { object.deinit(gpa); } - for (wasm.archives.items) |*archive| { - archive.deinit(gpa); - } + for (wasm.lazy_archives.items) |*lazy_archive| lazy_archive.deinit(gpa); + wasm.lazy_archives.deinit(gpa); if (wasm.findGlobalSymbol("__wasm_init_tls")) |loc| { const atom = wasm.symbol_atom.get(loc).?; @@ -1514,7 +1533,6 @@ pub fn deinit(wasm: *Wasm) void { wasm.data_segments.deinit(gpa); wasm.segment_info.deinit(gpa); wasm.objects.deinit(gpa); - wasm.archives.deinit(gpa); // free output sections wasm.imports.deinit(gpa); @@ -1527,6 +1545,7 @@ pub fn deinit(wasm: *Wasm) void { wasm.exports.deinit(gpa); wasm.string_table.deinit(gpa); + wasm.dump_argv_list.deinit(gpa); } pub fn updateFunc(wasm: *Wasm, pt: Zcu.PerThread, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { @@ -2584,7 +2603,7 @@ pub fn getMatchingSegment(wasm: *Wasm, object_id: ObjectId, symbol_index: Symbol } else { var err = try diags.addErrorWithNotes(1); try err.addMsg("found unknown section '{s}'", .{section_name}); - try err.addNote("defined in '{s}'", .{objectPath(wasm, object_id)}); + try err.addNote("defined in '{'}'", .{objectPath(wasm, object_id)}); return error.UnexpectedValue; } }, @@ -2603,6 +2622,32 @@ fn appendDummySegment(wasm: *Wasm) !void { }); } +pub fn loadInput(wasm: *Wasm, input: link.Input) !void { + const comp = wasm.base.comp; + const gpa = comp.gpa; + + if (comp.verbose_link) { + comp.mutex.lock(); // protect comp.arena + defer comp.mutex.unlock(); + + const argv = &wasm.dump_argv_list; + switch (input) { + .res => unreachable, + .dso_exact => unreachable, + .dso => unreachable, + .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)), + } + } + + switch (input) { + .res => unreachable, + .dso_exact => unreachable, + .dso => unreachable, + .object => |obj| try parseObject(wasm, obj), + .archive => |obj| try parseArchive(wasm, obj), + } +} + pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { const comp = wasm.base.comp; const use_lld = build_options.have_llvm and comp.config.use_lld; @@ -2613,7 +2658,6 @@ pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: st return wasm.flushModule(arena, tid, prog_node); } -/// Uses the in-house linker to link one or multiple object -and archive files into a WebAssembly binary. pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); @@ -2626,85 +2670,22 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_no if (use_lld) return; } + if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items); + const sub_prog_node = prog_node.start("Wasm Flush", 0); defer sub_prog_node.end(); - const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path}); - const module_obj_path: ?[]const u8 = if (wasm.base.zcu_object_sub_path) |path| blk: { - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, path }); - } else { - break :blk path; - } + const module_obj_path: ?Path = if (wasm.base.zcu_object_sub_path) |path| .{ + .root_dir = wasm.base.emit.root_dir, + .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname| + try fs.path.join(arena, &.{ dirname, path }) + else + path, } else null; - // Positional arguments to the linker such as object files and static archives. - // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. - var positionals = std.ArrayList([]const u8).init(arena); - try positionals.ensureUnusedCapacity(comp.link_inputs.len); + if (wasm.zig_object) |zig_object| try zig_object.flushModule(wasm, tid); - const target = comp.root_mod.resolved_target.result; - const output_mode = comp.config.output_mode; - const link_mode = comp.config.link_mode; - const link_libc = comp.config.link_libc; - const link_libcpp = comp.config.link_libcpp; - const wasi_exec_model = comp.config.wasi_exec_model; - - if (wasm.zig_object) |zig_object| { - try zig_object.flushModule(wasm, tid); - } - - // When the target os is WASI, we allow linking with WASI-LIBC - if (target.os.tag == .wasi) { - const is_exe_or_dyn_lib = output_mode == .Exe or - (output_mode == .Lib and link_mode == .dynamic); - if (is_exe_or_dyn_lib) { - for (comp.wasi_emulated_libs) |crt_file| { - try positionals.append(try comp.crtFileAsString( - arena, - wasi_libc.emulatedLibCRFileLibName(crt_file), - )); - } - - if (link_libc) { - try positionals.append(try comp.crtFileAsString( - arena, - wasi_libc.execModelCrtFileFullName(wasi_exec_model), - )); - try positionals.append(try comp.crtFileAsString(arena, "libc.a")); - } - - if (link_libcpp) { - try positionals.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena)); - try positionals.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena)); - } - } - } - - if (module_obj_path) |path| { - try positionals.append(path); - } - - for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive => |obj| try positionals.append(try obj.path.toString(arena)), - .dso => |dso| try positionals.append(try dso.path.toString(arena)), - .dso_exact => unreachable, // forbidden by frontend - .res => unreachable, // windows only - }; - - for (comp.c_object_table.keys()) |c_object| { - try positionals.append(try c_object.status.success.object_path.toString(arena)); - } - - if (comp.compiler_rt_lib) |lib| try positionals.append(try lib.full_object_path.toString(arena)); - if (comp.compiler_rt_obj) |obj| try positionals.append(try obj.full_object_path.toString(arena)); - - for (positionals.items) |path| { - if (try wasm.parseObjectFile(path)) continue; - if (try wasm.parseArchive(path, false)) continue; // load archives lazily - log.warn("Unexpected file format at path: '{s}'", .{path}); - } + if (module_obj_path) |path| openParseObjectReportingFailure(wasm, path); if (wasm.zig_object != null) { try wasm.resolveSymbolsInObject(.zig_object); @@ -3594,7 +3575,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: // regarding eliding redundant object -> object transformations. return error.NoObjectsToLink; }; - try std.fs.Dir.copyFile( + try fs.Dir.copyFile( the_object_path.root_dir.handle, the_object_path.sub_path, directory.handle, diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig index c7e5c7caba..bdbdec6f9a 100644 --- a/src/link/Wasm/Archive.zig +++ b/src/link/Wasm/Archive.zig @@ -1,18 +1,17 @@ -file: fs.File, -name: []const u8, - -header: ar_hdr = undefined, +header: ar_hdr, /// A list of long file names, delimited by a LF character (0x0a). /// This is stored as a single slice of bytes, as the header-names /// point to the character index of a file name, rather than the index /// in the list. -long_file_names: []const u8 = undefined, +long_file_names: []const u8, /// Parsed table of contents. /// Each symbol name points to a list of all definition /// sites within the current static archive. -toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .empty, +toc: Toc, + +const Toc = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)); // Archive files start with the ARMAG identifying string. Then follows a // `struct ar_hdr', and as many bytes of member file data as its `ar_size' @@ -82,35 +81,39 @@ const ar_hdr = extern struct { } }; -pub fn deinit(archive: *Archive, allocator: Allocator) void { - archive.file.close(); - for (archive.toc.keys()) |*key| { - allocator.free(key.*); - } - for (archive.toc.values()) |*value| { - value.deinit(allocator); - } - archive.toc.deinit(allocator); - allocator.free(archive.long_file_names); +pub fn deinit(archive: *Archive, gpa: Allocator) void { + deinitToc(gpa, &archive.toc); + gpa.free(archive.long_file_names); + archive.* = undefined; } -pub fn parse(archive: *Archive, allocator: Allocator) !void { - const reader = archive.file.reader(); +fn deinitToc(gpa: Allocator, toc: *Toc) void { + for (toc.keys()) |key| gpa.free(key); + for (toc.values()) |*value| value.deinit(gpa); + toc.deinit(gpa); +} + +pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive { + var fbs = std.io.fixedBufferStream(file_contents); + const reader = fbs.reader(); const magic = try reader.readBytesNoEof(SARMAG); - if (!mem.eql(u8, &magic, ARMAG)) { - log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic }); - return error.NotArchive; - } + if (!mem.eql(u8, &magic, ARMAG)) return error.BadArchiveMagic; - archive.header = try reader.readStruct(ar_hdr); - if (!mem.eql(u8, &archive.header.ar_fmag, ARFMAG)) { - log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, archive.header.ar_fmag }); - return error.NotArchive; - } + const header = try reader.readStruct(ar_hdr); + if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadHeaderDelimiter; - try archive.parseTableOfContents(allocator, reader); - try archive.parseNameTable(allocator, reader); + var toc = try parseTableOfContents(gpa, header, reader); + errdefer deinitToc(gpa, &toc); + + const long_file_names = try parseNameTable(gpa, reader); + errdefer gpa.free(long_file_names); + + return .{ + .header = header, + .toc = toc, + .long_file_names = long_file_names, + }; } fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 { @@ -124,24 +127,27 @@ fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 { } } -fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype) !void { +fn parseTableOfContents(gpa: Allocator, header: ar_hdr, reader: anytype) !Toc { // size field can have extra spaces padded in front as well as the end, // so we trim those first before parsing the ASCII value. - const size_trimmed = mem.trim(u8, &archive.header.ar_size, " "); + const size_trimmed = mem.trim(u8, &header.ar_size, " "); const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10); const num_symbols = try reader.readInt(u32, .big); - const symbol_positions = try allocator.alloc(u32, num_symbols); - defer allocator.free(symbol_positions); + const symbol_positions = try gpa.alloc(u32, num_symbols); + defer gpa.free(symbol_positions); for (symbol_positions) |*index| { index.* = try reader.readInt(u32, .big); } - const sym_tab = try allocator.alloc(u8, sym_tab_size - 4 - (4 * num_symbols)); - defer allocator.free(sym_tab); + const sym_tab = try gpa.alloc(u8, sym_tab_size - 4 - (4 * num_symbols)); + defer gpa.free(sym_tab); reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable; + var toc: Toc = .empty; + errdefer deinitToc(gpa, &toc); + var i: usize = 0; var pos: usize = 0; while (i < num_symbols) : (i += 1) { @@ -149,19 +155,21 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype pos += string.len + 1; if (string.len == 0) continue; - const name = try allocator.dupe(u8, string); - errdefer allocator.free(name); - const gop = try archive.toc.getOrPut(allocator, name); + const name = try gpa.dupe(u8, string); + errdefer gpa.free(name); + const gop = try toc.getOrPut(gpa, name); if (gop.found_existing) { - allocator.free(name); + gpa.free(name); } else { gop.value_ptr.* = .{}; } - try gop.value_ptr.append(allocator, symbol_positions[i]); + try gop.value_ptr.append(gpa, symbol_positions[i]); } + + return toc; } -fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void { +fn parseNameTable(gpa: Allocator, reader: anytype) ![]const u8 { const header: ar_hdr = try reader.readStruct(ar_hdr); if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) { return error.InvalidHeaderDelimiter; @@ -170,40 +178,25 @@ fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !voi return error.MissingTableName; } const table_size = try header.size(); - const long_file_names = try allocator.alloc(u8, table_size); - errdefer allocator.free(long_file_names); + const long_file_names = try gpa.alloc(u8, table_size); + errdefer gpa.free(long_file_names); try reader.readNoEof(long_file_names); - archive.long_file_names = long_file_names; + + return long_file_names; } /// From a given file offset, starts reading for a file header. /// When found, parses the object file into an `Object` and returns it. -pub fn parseObject(archive: Archive, wasm_file: *const Wasm, file_offset: u32) !Object { - const gpa = wasm_file.base.comp.gpa; - try archive.file.seekTo(file_offset); - const reader = archive.file.reader(); - const header = try reader.readStruct(ar_hdr); - const current_offset = try archive.file.getPos(); - try archive.file.seekTo(0); +pub fn parseObject(archive: Archive, wasm: *const Wasm, file_contents: []const u8, path: Path) !Object { + var fbs = std.io.fixedBufferStream(file_contents); + const header = try fbs.reader().readStruct(ar_hdr); - if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) { - return error.InvalidHeaderDelimiter; - } + if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) return error.BadArchiveHeaderDelimiter; const object_name = try archive.parseName(header); - const name = name: { - var buffer: [std.fs.max_path_bytes]u8 = undefined; - const path = try std.posix.realpath(archive.name, &buffer); - break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name }); - }; - defer gpa.free(name); - - const object_file = try std.fs.cwd().openFile(archive.name, .{}); - errdefer object_file.close(); - const object_file_size = try header.size(); - try object_file.seekTo(current_offset); - return Object.create(wasm_file, object_file, name, object_file_size); + + return Object.create(wasm, file_contents[@sizeOf(ar_hdr)..][0..object_file_size], path, object_name); } const std = @import("std"); @@ -211,6 +204,7 @@ const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.archive); const mem = std.mem; +const Path = std.Build.Cache.Path; const Allocator = mem.Allocator; const Object = @import("Object.zig"); diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index d6a86c3c62..8b0a847cc9 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -12,15 +12,19 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const leb = std.leb; const meta = std.meta; +const Path = std.Build.Cache.Path; const log = std.log.scoped(.object); /// Wasm spec version used for this `Object` version: u32 = 0, -/// The file descriptor that represents the wasm object file. -file: ?std.fs.File = null, -/// Name (read path) of the object file. -path: []const u8, +/// For error reporting purposes only. +/// Name (read path) of the object or archive file. +path: Path, +/// For error reporting purposes only. +/// If this represents an object in an archive, it's the basename of the +/// object, and path refers to the archive. +archive_member_name: ?[]const u8, /// Parsed type section func_types: []const std.wasm.Type = &.{}, /// A list of all imports for this module @@ -117,40 +121,28 @@ pub const RelocatableData = struct { } }; -pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadError; - /// Initializes a new `Object` from a wasm object file. /// This also parses and verifies the object file. /// When a max size is given, will only parse up to the given size, /// else will read until the end of the file. -pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object { - const gpa = wasm_file.base.comp.gpa; +pub fn create( + wasm: *const Wasm, + file_contents: []const u8, + path: Path, + archive_member_name: ?[]const u8, +) !Object { + const gpa = wasm.base.comp.gpa; var object: Object = .{ - .file = file, - .path = try gpa.dupe(u8, name), + .path = path, + .archive_member_name = archive_member_name, }; - var is_object_file: bool = false; - const size = maybe_max_size orelse size: { - errdefer gpa.free(object.path); - const stat = try file.stat(); - break :size @as(usize, @intCast(stat.size)); + var parser: Parser = .{ + .object = &object, + .wasm = wasm, + .reader = std.io.fixedBufferStream(file_contents), }; - - const file_contents = try gpa.alloc(u8, size); - defer gpa.free(file_contents); - var file_reader = file.reader(); - var read: usize = 0; - while (read < size) { - const n = try file_reader.read(file_contents[read..]); - std.debug.assert(n != 0); - read += n; - } - var fbs = std.io.fixedBufferStream(file_contents); - - try object.parse(gpa, wasm_file, fbs.reader(), &is_object_file); - errdefer object.deinit(gpa); - if (!is_object_file) return error.NotObjectFile; + try parser.parseObject(gpa); return object; } @@ -158,9 +150,6 @@ pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe /// Frees all memory of `Object` at once. The given `Allocator` must be /// the same allocator that was used when `init` was called. pub fn deinit(object: *Object, gpa: Allocator) void { - if (object.file) |file| { - file.close(); - } for (object.func_types) |func_ty| { gpa.free(func_ty.params); gpa.free(func_ty.returns); @@ -199,7 +188,6 @@ pub fn deinit(object: *Object, gpa: Allocator) void { } object.relocatable_data.deinit(gpa); object.string_table.deinit(gpa); - gpa.free(object.path); object.* = undefined; } @@ -221,8 +209,8 @@ pub fn findImport(object: *const Object, sym: Symbol) Wasm.Import { /// we initialize a new table symbol that corresponds to that import and return that symbol. /// /// When the object file is *NOT* MVP, we return `null`. -fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?Symbol { - const diags = &wasm_file.base.comp.link_diags; +fn checkLegacyIndirectFunctionTable(object: *Object, wasm: *const Wasm) !?Symbol { + const diags = &wasm.base.comp.link_diags; var table_count: usize = 0; for (object.symtable) |sym| { @@ -233,28 +221,19 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S if (object.imported_tables_count == table_count) return null; if (table_count != 0) { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ + return diags.failParse(object.path, "expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ object.imported_tables_count, table_count, }); - try err.addNote("defined in '{s}'", .{object.path}); - return error.MissingTableSymbols; } // MVP object files cannot have any table definitions, only imports (for the indirect function table). if (object.tables.len > 0) { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("Unexpected table definition without representing table symbols.", .{}); - try err.addNote("defined in '{s}'", .{object.path}); - return error.UnexpectedTable; + return diags.failParse(object.path, "unexpected table definition without representing table symbols.", .{}); } if (object.imported_tables_count != 1) { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("Found more than one table import, but no representing table symbols", .{}); - try err.addNote("defined in '{s}'", .{object.path}); - return error.MissingTableSymbols; + return diags.failParse(object.path, "found more than one table import, but no representing table symbols", .{}); } const table_import: Wasm.Import = for (object.imports) |imp| { @@ -264,10 +243,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S } else unreachable; if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)}); - try err.addNote("defined in '{s}'", .{object.path}); - return error.MissingTableSymbols; + return diags.failParse(object.path, "non-indirect function table import '{s}' is missing a corresponding symbol", .{ + object.string_table.get(table_import.name), + }); } var table_symbol: Symbol = .{ @@ -282,576 +260,518 @@ fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?S return table_symbol; } -/// Error set containing parsing errors. -/// Merged with reader's errorset by `Parser` -pub const ParseError = error{ - /// The magic byte is either missing or does not contain \0Asm - InvalidMagicByte, - /// The wasm version is either missing or does not match the supported version. - InvalidWasmVersion, - /// Expected the functype byte while parsing the Type section but did not find it. - ExpectedFuncType, - /// Missing an 'end' opcode when defining a constant expression. - MissingEndForExpression, - /// Missing an 'end' opcode at the end of a body expression. - MissingEndForBody, - /// The size defined in the section code mismatches with the actual payload size. - MalformedSection, - /// Stream has reached the end. Unreachable for caller and must be handled internally - /// by the parser. - EndOfStream, - /// Ran out of memory when allocating. - OutOfMemory, - /// A non-zero flag was provided for comdat info - UnexpectedValue, - /// An import symbol contains an index to an import that does - /// not exist, or no imports were defined. - InvalidIndex, - /// The section "linking" contains a version that is not supported. - UnsupportedVersion, - /// When reading the data in leb128 compressed format, its value was overflown. - Overflow, - /// Found table definitions but no corresponding table symbols - MissingTableSymbols, - /// Did not expect a table definition, but did find one - UnexpectedTable, - /// Object file contains a feature that is unknown to the linker - UnknownFeature, -}; +const Parser = struct { + reader: std.io.FixedBufferStream([]const u8), + /// Object file we're building + object: *Object, + /// Read-only reference to the WebAssembly linker + wasm: *const Wasm, -fn parse(object: *Object, gpa: Allocator, wasm_file: *const Wasm, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void { - var parser = Parser(@TypeOf(reader)).init(object, wasm_file, reader); - return parser.parseObject(gpa, is_object_file); -} - -fn Parser(comptime ReaderType: type) type { - return struct { - const ObjectParser = @This(); - const Error = ReaderType.Error || ParseError; - - reader: std.io.CountingReader(ReaderType), - /// Object file we're building - object: *Object, - /// Read-only reference to the WebAssembly linker - wasm_file: *const Wasm, - - fn init(object: *Object, wasm_file: *const Wasm, reader: ReaderType) ObjectParser { - return .{ .object = object, .wasm_file = wasm_file, .reader = std.io.countingReader(reader) }; - } - - /// Verifies that the first 4 bytes contains \0Asm - fn verifyMagicBytes(parser: *ObjectParser) Error!void { + fn parseObject(parser: *Parser, gpa: Allocator) anyerror!void { + { var magic_bytes: [4]u8 = undefined; - try parser.reader.reader().readNoEof(&magic_bytes); - if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) { - log.debug("Invalid magic bytes '{s}'", .{&magic_bytes}); - return error.InvalidMagicByte; - } + if (!std.mem.eql(u8, &magic_bytes, &std.wasm.magic)) return error.BadObjectMagic; } - fn parseObject(parser: *ObjectParser, gpa: Allocator, is_object_file: *bool) Error!void { - errdefer parser.object.deinit(gpa); - try parser.verifyMagicBytes(); - const version = try parser.reader.reader().readInt(u32, .little); - parser.object.version = version; + const version = try parser.reader.reader().readInt(u32, .little); + parser.object.version = version; + + var saw_linking_section = false; + + var section_index: u32 = 0; + while (parser.reader.reader().readByte()) |byte| : (section_index += 1) { + const len = try readLeb(u32, parser.reader.reader()); + var limited_reader = std.io.limitedReader(parser.reader.reader(), len); + const reader = limited_reader.reader(); + switch (@as(std.wasm.Section, @enumFromInt(byte))) { + .custom => { + const name_len = try readLeb(u32, reader); + const name = try gpa.alloc(u8, name_len); + defer gpa.free(name); + try reader.readNoEof(name); + + if (std.mem.eql(u8, name, "linking")) { + saw_linking_section = true; + try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left))); + } else if (std.mem.startsWith(u8, name, "reloc")) { + try parser.parseRelocations(gpa); + } else if (std.mem.eql(u8, name, "target_features")) { + try parser.parseFeatures(gpa); + } else if (std.mem.startsWith(u8, name, ".debug")) { + const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom); + var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .empty; + defer relocatable_data.deinit(gpa); + if (!gop.found_existing) { + gop.value_ptr.* = &.{}; + } else { + relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*); + } + const debug_size = @as(u32, @intCast(reader.context.bytes_left)); + const debug_content = try gpa.alloc(u8, debug_size); + errdefer gpa.free(debug_content); + try reader.readNoEof(debug_content); + + try relocatable_data.append(gpa, .{ + .type = .custom, + .data = debug_content.ptr, + .size = debug_size, + .index = try parser.object.string_table.put(gpa, name), + .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset + .section_index = section_index, + }); + gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa); + } else { + try reader.skipBytes(reader.context.bytes_left, .{}); + } + }, + .type => { + for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| { + if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType; + + for (try readVec(&type_val.params, reader, gpa)) |*param| { + param.* = try readEnum(std.wasm.Valtype, reader); + } + + for (try readVec(&type_val.returns, reader, gpa)) |*result| { + result.* = try readEnum(std.wasm.Valtype, reader); + } + } + try assertEnd(reader); + }, + .import => { + for (try readVec(&parser.object.imports, reader, gpa)) |*import| { + const module_len = try readLeb(u32, reader); + const module_name = try gpa.alloc(u8, module_len); + defer gpa.free(module_name); + try reader.readNoEof(module_name); - var section_index: u32 = 0; - while (parser.reader.reader().readByte()) |byte| : (section_index += 1) { - const len = try readLeb(u32, parser.reader.reader()); - var limited_reader = std.io.limitedReader(parser.reader.reader(), len); - const reader = limited_reader.reader(); - switch (@as(std.wasm.Section, @enumFromInt(byte))) { - .custom => { const name_len = try readLeb(u32, reader); const name = try gpa.alloc(u8, name_len); defer gpa.free(name); try reader.readNoEof(name); - if (std.mem.eql(u8, name, "linking")) { - is_object_file.* = true; - try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left))); - } else if (std.mem.startsWith(u8, name, "reloc")) { - try parser.parseRelocations(gpa); - } else if (std.mem.eql(u8, name, "target_features")) { - try parser.parseFeatures(gpa); - } else if (std.mem.startsWith(u8, name, ".debug")) { - const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom); - var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .empty; - defer relocatable_data.deinit(gpa); - if (!gop.found_existing) { - gop.value_ptr.* = &.{}; - } else { - relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*); - } - const debug_size = @as(u32, @intCast(reader.context.bytes_left)); - const debug_content = try gpa.alloc(u8, debug_size); - errdefer gpa.free(debug_content); - try reader.readNoEof(debug_content); - - try relocatable_data.append(gpa, .{ - .type = .custom, - .data = debug_content.ptr, - .size = debug_size, - .index = try parser.object.string_table.put(gpa, name), - .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset - .section_index = section_index, - }); - gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa); - } else { - try reader.skipBytes(reader.context.bytes_left, .{}); - } - }, - .type => { - for (try readVec(&parser.object.func_types, reader, gpa)) |*type_val| { - if ((try reader.readByte()) != std.wasm.function_type) return error.ExpectedFuncType; - - for (try readVec(&type_val.params, reader, gpa)) |*param| { - param.* = try readEnum(std.wasm.Valtype, reader); - } - - for (try readVec(&type_val.returns, reader, gpa)) |*result| { - result.* = try readEnum(std.wasm.Valtype, reader); - } - } - try assertEnd(reader); - }, - .import => { - for (try readVec(&parser.object.imports, reader, gpa)) |*import| { - const module_len = try readLeb(u32, reader); - const module_name = try gpa.alloc(u8, module_len); - defer gpa.free(module_name); - try reader.readNoEof(module_name); - - const name_len = try readLeb(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - - const kind = try readEnum(std.wasm.ExternalKind, reader); - const kind_value: std.wasm.Import.Kind = switch (kind) { - .function => val: { - parser.object.imported_functions_count += 1; - break :val .{ .function = try readLeb(u32, reader) }; - }, - .memory => .{ .memory = try readLimits(reader) }, - .global => val: { - parser.object.imported_globals_count += 1; - break :val .{ .global = .{ - .valtype = try readEnum(std.wasm.Valtype, reader), - .mutable = (try reader.readByte()) == 0x01, - } }; - }, - .table => val: { - parser.object.imported_tables_count += 1; - break :val .{ .table = .{ - .reftype = try readEnum(std.wasm.RefType, reader), - .limits = try readLimits(reader), - } }; - }, - }; - - import.* = .{ - .module_name = try parser.object.string_table.put(gpa, module_name), - .name = try parser.object.string_table.put(gpa, name), - .kind = kind_value, - }; - } - try assertEnd(reader); - }, - .function => { - for (try readVec(&parser.object.functions, reader, gpa)) |*func| { - func.* = .{ .type_index = try readLeb(u32, reader) }; - } - try assertEnd(reader); - }, - .table => { - for (try readVec(&parser.object.tables, reader, gpa)) |*table| { - table.* = .{ - .reftype = try readEnum(std.wasm.RefType, reader), - .limits = try readLimits(reader), - }; - } - try assertEnd(reader); - }, - .memory => { - for (try readVec(&parser.object.memories, reader, gpa)) |*memory| { - memory.* = .{ .limits = try readLimits(reader) }; - } - try assertEnd(reader); - }, - .global => { - for (try readVec(&parser.object.globals, reader, gpa)) |*global| { - global.* = .{ - .global_type = .{ + const kind = try readEnum(std.wasm.ExternalKind, reader); + const kind_value: std.wasm.Import.Kind = switch (kind) { + .function => val: { + parser.object.imported_functions_count += 1; + break :val .{ .function = try readLeb(u32, reader) }; + }, + .memory => .{ .memory = try readLimits(reader) }, + .global => val: { + parser.object.imported_globals_count += 1; + break :val .{ .global = .{ .valtype = try readEnum(std.wasm.Valtype, reader), .mutable = (try reader.readByte()) == 0x01, - }, - .init = try readInit(reader), - }; - } - try assertEnd(reader); - }, - .@"export" => { - for (try readVec(&parser.object.exports, reader, gpa)) |*exp| { - const name_len = try readLeb(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - exp.* = .{ - .name = try parser.object.string_table.put(gpa, name), - .kind = try readEnum(std.wasm.ExternalKind, reader), - .index = try readLeb(u32, reader), - }; - } - try assertEnd(reader); - }, - .start => { - parser.object.start = try readLeb(u32, reader); - try assertEnd(reader); - }, - .element => { - for (try readVec(&parser.object.elements, reader, gpa)) |*elem| { - elem.table_index = try readLeb(u32, reader); - elem.offset = try readInit(reader); + } }; + }, + .table => val: { + parser.object.imported_tables_count += 1; + break :val .{ .table = .{ + .reftype = try readEnum(std.wasm.RefType, reader), + .limits = try readLimits(reader), + } }; + }, + }; - for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| { - idx.* = try readLeb(u32, reader); - } + import.* = .{ + .module_name = try parser.object.string_table.put(gpa, module_name), + .name = try parser.object.string_table.put(gpa, name), + .kind = kind_value, + }; + } + try assertEnd(reader); + }, + .function => { + for (try readVec(&parser.object.functions, reader, gpa)) |*func| { + func.* = .{ .type_index = try readLeb(u32, reader) }; + } + try assertEnd(reader); + }, + .table => { + for (try readVec(&parser.object.tables, reader, gpa)) |*table| { + table.* = .{ + .reftype = try readEnum(std.wasm.RefType, reader), + .limits = try readLimits(reader), + }; + } + try assertEnd(reader); + }, + .memory => { + for (try readVec(&parser.object.memories, reader, gpa)) |*memory| { + memory.* = .{ .limits = try readLimits(reader) }; + } + try assertEnd(reader); + }, + .global => { + for (try readVec(&parser.object.globals, reader, gpa)) |*global| { + global.* = .{ + .global_type = .{ + .valtype = try readEnum(std.wasm.Valtype, reader), + .mutable = (try reader.readByte()) == 0x01, + }, + .init = try readInit(reader), + }; + } + try assertEnd(reader); + }, + .@"export" => { + for (try readVec(&parser.object.exports, reader, gpa)) |*exp| { + const name_len = try readLeb(u32, reader); + const name = try gpa.alloc(u8, name_len); + defer gpa.free(name); + try reader.readNoEof(name); + exp.* = .{ + .name = try parser.object.string_table.put(gpa, name), + .kind = try readEnum(std.wasm.ExternalKind, reader), + .index = try readLeb(u32, reader), + }; + } + try assertEnd(reader); + }, + .start => { + parser.object.start = try readLeb(u32, reader); + try assertEnd(reader); + }, + .element => { + for (try readVec(&parser.object.elements, reader, gpa)) |*elem| { + elem.table_index = try readLeb(u32, reader); + elem.offset = try readInit(reader); + + for (try readVec(&elem.func_indexes, reader, gpa)) |*idx| { + idx.* = try readLeb(u32, reader); } - try assertEnd(reader); - }, - .code => { - const start = reader.context.bytes_left; - var index: u32 = 0; - const count = try readLeb(u32, reader); - const imported_function_count = parser.object.imported_functions_count; - var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count); - defer relocatable_data.deinit(); - while (index < count) : (index += 1) { - const code_len = try readLeb(u32, reader); - const offset = @as(u32, @intCast(start - reader.context.bytes_left)); - const data = try gpa.alloc(u8, code_len); - errdefer gpa.free(data); - try reader.readNoEof(data); - relocatable_data.appendAssumeCapacity(.{ - .type = .code, - .data = data.ptr, - .size = code_len, - .index = imported_function_count + index, - .offset = offset, - .section_index = section_index, - }); - } - try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice()); - }, - .data => { - const start = reader.context.bytes_left; - var index: u32 = 0; - const count = try readLeb(u32, reader); - var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count); - defer relocatable_data.deinit(); - while (index < count) : (index += 1) { - const flags = try readLeb(u32, reader); - const data_offset = try readInit(reader); - _ = flags; // TODO: Do we need to check flags to detect passive/active memory? - _ = data_offset; - const data_len = try readLeb(u32, reader); - const offset = @as(u32, @intCast(start - reader.context.bytes_left)); - const data = try gpa.alloc(u8, data_len); - errdefer gpa.free(data); - try reader.readNoEof(data); - relocatable_data.appendAssumeCapacity(.{ - .type = .data, - .data = data.ptr, - .size = data_len, - .index = index, - .offset = offset, - .section_index = section_index, - }); - } - try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice()); - }, - else => try parser.reader.reader().skipBytes(len, .{}), - } - } else |err| switch (err) { - error.EndOfStream => {}, // finished parsing the file - else => |e| return e, + } + try assertEnd(reader); + }, + .code => { + const start = reader.context.bytes_left; + var index: u32 = 0; + const count = try readLeb(u32, reader); + const imported_function_count = parser.object.imported_functions_count; + var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count); + defer relocatable_data.deinit(); + while (index < count) : (index += 1) { + const code_len = try readLeb(u32, reader); + const offset = @as(u32, @intCast(start - reader.context.bytes_left)); + const data = try gpa.alloc(u8, code_len); + errdefer gpa.free(data); + try reader.readNoEof(data); + relocatable_data.appendAssumeCapacity(.{ + .type = .code, + .data = data.ptr, + .size = code_len, + .index = imported_function_count + index, + .offset = offset, + .section_index = section_index, + }); + } + try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice()); + }, + .data => { + const start = reader.context.bytes_left; + var index: u32 = 0; + const count = try readLeb(u32, reader); + var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count); + defer relocatable_data.deinit(); + while (index < count) : (index += 1) { + const flags = try readLeb(u32, reader); + const data_offset = try readInit(reader); + _ = flags; // TODO: Do we need to check flags to detect passive/active memory? + _ = data_offset; + const data_len = try readLeb(u32, reader); + const offset = @as(u32, @intCast(start - reader.context.bytes_left)); + const data = try gpa.alloc(u8, data_len); + errdefer gpa.free(data); + try reader.readNoEof(data); + relocatable_data.appendAssumeCapacity(.{ + .type = .data, + .data = data.ptr, + .size = data_len, + .index = index, + .offset = offset, + .section_index = section_index, + }); + } + try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice()); + }, + else => try parser.reader.reader().skipBytes(len, .{}), } + } else |err| switch (err) { + error.EndOfStream => {}, // finished parsing the file + else => |e| return e, + } + if (!saw_linking_section) return error.MissingLinkingSection; + } + + /// Based on the "features" custom section, parses it into a list of + /// features that tell the linker what features were enabled and may be mandatory + /// to be able to link. + /// Logs an info message when an undefined feature is detected. + fn parseFeatures(parser: *Parser, gpa: Allocator) !void { + const diags = &parser.wasm.base.comp.link_diags; + const reader = parser.reader.reader(); + for (try readVec(&parser.object.features, reader, gpa)) |*feature| { + const prefix = try readEnum(Wasm.Feature.Prefix, reader); + const name_len = try leb.readUleb128(u32, reader); + const name = try gpa.alloc(u8, name_len); + defer gpa.free(name); + try reader.readNoEof(name); + + const tag = Wasm.known_features.get(name) orelse { + return diags.failParse(parser.object.path, "object file contains unknown feature: {s}", .{name}); + }; + feature.* = .{ + .prefix = prefix, + .tag = tag, + }; + } + } + + /// Parses a "reloc" custom section into a list of relocations. + /// The relocations are mapped into `Object` where the key is the section + /// they apply to. + fn parseRelocations(parser: *Parser, gpa: Allocator) !void { + const reader = parser.reader.reader(); + const section = try leb.readUleb128(u32, reader); + const count = try leb.readUleb128(u32, reader); + const relocations = try gpa.alloc(Wasm.Relocation, count); + errdefer gpa.free(relocations); + + log.debug("Found {d} relocations for section ({d})", .{ + count, + section, + }); + + for (relocations) |*relocation| { + const rel_type = try reader.readByte(); + const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection; + relocation.* = .{ + .relocation_type = rel_type_enum, + .offset = try leb.readUleb128(u32, reader), + .index = try leb.readUleb128(u32, reader), + .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0, + }; + log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{ + @tagName(relocation.relocation_type), + relocation.offset, + relocation.index, + relocation.addend, + }); } - /// Based on the "features" custom section, parses it into a list of - /// features that tell the linker what features were enabled and may be mandatory - /// to be able to link. - /// Logs an info message when an undefined feature is detected. - fn parseFeatures(parser: *ObjectParser, gpa: Allocator) !void { - const diags = &parser.wasm_file.base.comp.link_diags; - const reader = parser.reader.reader(); - for (try readVec(&parser.object.features, reader, gpa)) |*feature| { - const prefix = try readEnum(Wasm.Feature.Prefix, reader); + try parser.object.relocations.putNoClobber(gpa, section, relocations); + } + + /// Parses the "linking" custom section. Versions that are not + /// supported will be an error. `payload_size` is required to be able + /// to calculate the subsections we need to parse, as that data is not + /// available within the section itparser. + fn parseMetadata(parser: *Parser, gpa: Allocator, payload_size: usize) !void { + var limited = std.io.limitedReader(parser.reader.reader(), payload_size); + const limited_reader = limited.reader(); + + const version = try leb.readUleb128(u32, limited_reader); + log.debug("Link meta data version: {d}", .{version}); + if (version != 2) return error.UnsupportedVersion; + + while (limited.bytes_left > 0) { + try parser.parseSubsection(gpa, limited_reader); + } + } + + /// Parses a `spec.Subsection`. + /// The `reader` param for this is to provide a `LimitedReader`, which allows + /// us to only read until a max length. + /// + /// `parser` is used to provide access to other sections that may be needed, + /// such as access to the `import` section to find the name of a symbol. + fn parseSubsection(parser: *Parser, gpa: Allocator, reader: anytype) !void { + const sub_type = try leb.readUleb128(u8, reader); + log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))}); + const payload_len = try leb.readUleb128(u32, reader); + if (payload_len == 0) return; + + var limited = std.io.limitedReader(reader, payload_len); + const limited_reader = limited.reader(); + + // every subsection contains a 'count' field + const count = try leb.readUleb128(u32, limited_reader); + + switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) { + .WASM_SEGMENT_INFO => { + const segments = try gpa.alloc(Wasm.NamedSegment, count); + errdefer gpa.free(segments); + for (segments) |*segment| { + const name_len = try leb.readUleb128(u32, reader); + const name = try gpa.alloc(u8, name_len); + errdefer gpa.free(name); + try reader.readNoEof(name); + segment.* = .{ + .name = name, + .alignment = @enumFromInt(try leb.readUleb128(u32, reader)), + .flags = try leb.readUleb128(u32, reader), + }; + log.debug("Found segment: {s} align({d}) flags({b})", .{ + segment.name, + segment.alignment, + segment.flags, + }); + + // support legacy object files that specified being TLS by the name instead of the TLS flag. + if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) { + // set the flag so we can simply check for the flag in the rest of the linker. + segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS); + } + } + parser.object.segment_info = segments; + }, + .WASM_INIT_FUNCS => { + const funcs = try gpa.alloc(Wasm.InitFunc, count); + errdefer gpa.free(funcs); + for (funcs) |*func| { + func.* = .{ + .priority = try leb.readUleb128(u32, reader), + .symbol_index = try leb.readUleb128(u32, reader), + }; + log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index }); + } + parser.object.init_funcs = funcs; + }, + .WASM_COMDAT_INFO => { + const comdats = try gpa.alloc(Wasm.Comdat, count); + errdefer gpa.free(comdats); + for (comdats) |*comdat| { + const name_len = try leb.readUleb128(u32, reader); + const name = try gpa.alloc(u8, name_len); + errdefer gpa.free(name); + try reader.readNoEof(name); + + const flags = try leb.readUleb128(u32, reader); + if (flags != 0) { + return error.UnexpectedValue; + } + + const symbol_count = try leb.readUleb128(u32, reader); + const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count); + errdefer gpa.free(symbols); + for (symbols) |*symbol| { + symbol.* = .{ + .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))), + .index = try leb.readUleb128(u32, reader), + }; + } + + comdat.* = .{ + .name = name, + .flags = flags, + .symbols = symbols, + }; + } + + parser.object.comdat_info = comdats; + }, + .WASM_SYMBOL_TABLE => { + var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count); + + var i: usize = 0; + while (i < count) : (i += 1) { + const symbol = symbols.addOneAssumeCapacity(); + symbol.* = try parser.parseSymbol(gpa, reader); + log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{ + @tagName(symbol.tag), + parser.object.string_table.get(symbol.name), + symbol.flags, + }); + } + + // we found all symbols, check for indirect function table + // in case of an MVP object file + if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm)) |symbol| { + try symbols.append(symbol); + log.debug("Found legacy indirect function table. Created symbol", .{}); + } + + // Not all debug sections may be represented by a symbol, for those sections + // we manually create a symbol. + if (parser.object.relocatable_data.get(.custom)) |custom_sections| { + for (custom_sections) |*data| { + if (!data.represented) { + try symbols.append(.{ + .name = data.index, + .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), + .tag = .section, + .virtual_address = 0, + .index = data.section_index, + }); + data.represented = true; + log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.index)}); + } + } + } + + parser.object.symtable = try symbols.toOwnedSlice(); + }, + } + } + + /// Parses the symbol information based on its kind, + /// requires access to `Object` to find the name of a symbol when it's + /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set. + fn parseSymbol(parser: *Parser, gpa: Allocator, reader: anytype) !Symbol { + const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader))); + const flags = try leb.readUleb128(u32, reader); + var symbol: Symbol = .{ + .flags = flags, + .tag = tag, + .name = undefined, + .index = undefined, + .virtual_address = undefined, + }; + + switch (tag) { + .data => { const name_len = try leb.readUleb128(u32, reader); const name = try gpa.alloc(u8, name_len); defer gpa.free(name); try reader.readNoEof(name); + symbol.name = try parser.object.string_table.put(gpa, name); - const tag = Wasm.known_features.get(name) orelse { - var err = try diags.addErrorWithNotes(1); - try err.addMsg("Object file contains unknown feature: {s}", .{name}); - try err.addNote("defined in '{s}'", .{parser.object.path}); - return error.UnknownFeature; - }; - feature.* = .{ - .prefix = prefix, - .tag = tag, - }; - } - } - - /// Parses a "reloc" custom section into a list of relocations. - /// The relocations are mapped into `Object` where the key is the section - /// they apply to. - fn parseRelocations(parser: *ObjectParser, gpa: Allocator) !void { - const reader = parser.reader.reader(); - const section = try leb.readUleb128(u32, reader); - const count = try leb.readUleb128(u32, reader); - const relocations = try gpa.alloc(Wasm.Relocation, count); - errdefer gpa.free(relocations); - - log.debug("Found {d} relocations for section ({d})", .{ - count, - section, - }); - - for (relocations) |*relocation| { - const rel_type = try reader.readByte(); - const rel_type_enum = std.meta.intToEnum(Wasm.Relocation.RelocationType, rel_type) catch return error.MalformedSection; - relocation.* = .{ - .relocation_type = rel_type_enum, - .offset = try leb.readUleb128(u32, reader), - .index = try leb.readUleb128(u32, reader), - .addend = if (rel_type_enum.addendIsPresent()) try leb.readIleb128(i32, reader) else 0, - }; - log.debug("Found relocation: type({s}) offset({d}) index({d}) addend({?d})", .{ - @tagName(relocation.relocation_type), - relocation.offset, - relocation.index, - relocation.addend, - }); - } - - try parser.object.relocations.putNoClobber(gpa, section, relocations); - } - - /// Parses the "linking" custom section. Versions that are not - /// supported will be an error. `payload_size` is required to be able - /// to calculate the subsections we need to parse, as that data is not - /// available within the section itparser. - fn parseMetadata(parser: *ObjectParser, gpa: Allocator, payload_size: usize) !void { - var limited = std.io.limitedReader(parser.reader.reader(), payload_size); - const limited_reader = limited.reader(); - - const version = try leb.readUleb128(u32, limited_reader); - log.debug("Link meta data version: {d}", .{version}); - if (version != 2) return error.UnsupportedVersion; - - while (limited.bytes_left > 0) { - try parser.parseSubsection(gpa, limited_reader); - } - } - - /// Parses a `spec.Subsection`. - /// The `reader` param for this is to provide a `LimitedReader`, which allows - /// us to only read until a max length. - /// - /// `parser` is used to provide access to other sections that may be needed, - /// such as access to the `import` section to find the name of a symbol. - fn parseSubsection(parser: *ObjectParser, gpa: Allocator, reader: anytype) !void { - const sub_type = try leb.readUleb128(u8, reader); - log.debug("Found subsection: {s}", .{@tagName(@as(Wasm.SubsectionType, @enumFromInt(sub_type)))}); - const payload_len = try leb.readUleb128(u32, reader); - if (payload_len == 0) return; - - var limited = std.io.limitedReader(reader, payload_len); - const limited_reader = limited.reader(); - - // every subsection contains a 'count' field - const count = try leb.readUleb128(u32, limited_reader); - - switch (@as(Wasm.SubsectionType, @enumFromInt(sub_type))) { - .WASM_SEGMENT_INFO => { - const segments = try gpa.alloc(Wasm.NamedSegment, count); - errdefer gpa.free(segments); - for (segments) |*segment| { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - errdefer gpa.free(name); - try reader.readNoEof(name); - segment.* = .{ - .name = name, - .alignment = @enumFromInt(try leb.readUleb128(u32, reader)), - .flags = try leb.readUleb128(u32, reader), - }; - log.debug("Found segment: {s} align({d}) flags({b})", .{ - segment.name, - segment.alignment, - segment.flags, - }); - - // support legacy object files that specified being TLS by the name instead of the TLS flag. - if (!segment.isTLS() and (std.mem.startsWith(u8, segment.name, ".tdata") or std.mem.startsWith(u8, segment.name, ".tbss"))) { - // set the flag so we can simply check for the flag in the rest of the linker. - segment.flags |= @intFromEnum(Wasm.NamedSegment.Flags.WASM_SEG_FLAG_TLS); - } + // Data symbols only have the following fields if the symbol is defined + if (symbol.isDefined()) { + symbol.index = try leb.readUleb128(u32, reader); + // @TODO: We should verify those values + _ = try leb.readUleb128(u32, reader); + _ = try leb.readUleb128(u32, reader); + } + }, + .section => { + symbol.index = try leb.readUleb128(u32, reader); + const section_data = parser.object.relocatable_data.get(.custom).?; + for (section_data) |*data| { + if (data.section_index == symbol.index) { + symbol.name = data.index; + data.represented = true; + break; } - parser.object.segment_info = segments; - }, - .WASM_INIT_FUNCS => { - const funcs = try gpa.alloc(Wasm.InitFunc, count); - errdefer gpa.free(funcs); - for (funcs) |*func| { - func.* = .{ - .priority = try leb.readUleb128(u32, reader), - .symbol_index = try leb.readUleb128(u32, reader), - }; - log.debug("Found function - prio: {d}, index: {d}", .{ func.priority, func.symbol_index }); - } - parser.object.init_funcs = funcs; - }, - .WASM_COMDAT_INFO => { - const comdats = try gpa.alloc(Wasm.Comdat, count); - errdefer gpa.free(comdats); - for (comdats) |*comdat| { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - errdefer gpa.free(name); - try reader.readNoEof(name); - - const flags = try leb.readUleb128(u32, reader); - if (flags != 0) { - return error.UnexpectedValue; - } - - const symbol_count = try leb.readUleb128(u32, reader); - const symbols = try gpa.alloc(Wasm.ComdatSym, symbol_count); - errdefer gpa.free(symbols); - for (symbols) |*symbol| { - symbol.* = .{ - .kind = @as(Wasm.ComdatSym.Type, @enumFromInt(try leb.readUleb128(u8, reader))), - .index = try leb.readUleb128(u32, reader), - }; - } - - comdat.* = .{ - .name = name, - .flags = flags, - .symbols = symbols, - }; - } - - parser.object.comdat_info = comdats; - }, - .WASM_SYMBOL_TABLE => { - var symbols = try std.ArrayList(Symbol).initCapacity(gpa, count); - - var i: usize = 0; - while (i < count) : (i += 1) { - const symbol = symbols.addOneAssumeCapacity(); - symbol.* = try parser.parseSymbol(gpa, reader); - log.debug("Found symbol: type({s}) name({s}) flags(0b{b:0>8})", .{ - @tagName(symbol.tag), - parser.object.string_table.get(symbol.name), - symbol.flags, - }); - } - - // we found all symbols, check for indirect function table - // in case of an MVP object file - if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm_file)) |symbol| { - try symbols.append(symbol); - log.debug("Found legacy indirect function table. Created symbol", .{}); - } - - // Not all debug sections may be represented by a symbol, for those sections - // we manually create a symbol. - if (parser.object.relocatable_data.get(.custom)) |custom_sections| { - for (custom_sections) |*data| { - if (!data.represented) { - try symbols.append(.{ - .name = data.index, - .flags = @intFromEnum(Symbol.Flag.WASM_SYM_BINDING_LOCAL), - .tag = .section, - .virtual_address = 0, - .index = data.section_index, - }); - data.represented = true; - log.debug("Created synthetic custom section symbol for '{s}'", .{parser.object.string_table.get(data.index)}); - } - } - } - - parser.object.symtable = try symbols.toOwnedSlice(); - }, - } - } - - /// Parses the symbol information based on its kind, - /// requires access to `Object` to find the name of a symbol when it's - /// an import and flag `WASM_SYM_EXPLICIT_NAME` is not set. - fn parseSymbol(parser: *ObjectParser, gpa: Allocator, reader: anytype) !Symbol { - const tag = @as(Symbol.Tag, @enumFromInt(try leb.readUleb128(u8, reader))); - const flags = try leb.readUleb128(u32, reader); - var symbol: Symbol = .{ - .flags = flags, - .tag = tag, - .name = undefined, - .index = undefined, - .virtual_address = undefined, - }; - - switch (tag) { - .data => { + } + }, + else => { + symbol.index = try leb.readUleb128(u32, reader); + const is_undefined = symbol.isUndefined(); + const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME); + symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: { const name_len = try leb.readUleb128(u32, reader); const name = try gpa.alloc(u8, name_len); defer gpa.free(name); try reader.readNoEof(name); - symbol.name = try parser.object.string_table.put(gpa, name); - - // Data symbols only have the following fields if the symbol is defined - if (symbol.isDefined()) { - symbol.index = try leb.readUleb128(u32, reader); - // @TODO: We should verify those values - _ = try leb.readUleb128(u32, reader); - _ = try leb.readUleb128(u32, reader); - } - }, - .section => { - symbol.index = try leb.readUleb128(u32, reader); - const section_data = parser.object.relocatable_data.get(.custom).?; - for (section_data) |*data| { - if (data.section_index == symbol.index) { - symbol.name = data.index; - data.represented = true; - break; - } - } - }, - else => { - symbol.index = try leb.readUleb128(u32, reader); - const is_undefined = symbol.isUndefined(); - const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME); - symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: { - const name_len = try leb.readUleb128(u32, reader); - const name = try gpa.alloc(u8, name_len); - defer gpa.free(name); - try reader.readNoEof(name); - break :name try parser.object.string_table.put(gpa, name); - } else parser.object.findImport(symbol).name; - }, - } - return symbol; + break :name try parser.object.string_table.put(gpa, name); + } else parser.object.findImport(symbol).name; + }, } - }; -} + return symbol; + } +}; /// First reads the count from the reader and then allocate /// a slice of ptr child's element type. diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index 56e389efd3..437c6479b3 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -1,9 +1,9 @@ //! ZigObject encapsulates the state of the incrementally compiled Zig module. //! It stores the associated input local and global symbols, allocated atoms, //! and any relocations that may have been emitted. -//! Think about this as fake in-memory Object file for the Zig module. -path: []const u8, +/// For error reporting purposes only. +path: Path, /// Map of all `Nav` that are currently alive. /// Each index maps to the corresponding `NavInfo`. navs: std.AutoHashMapUnmanaged(InternPool.Nav.Index, NavInfo) = .empty, @@ -210,7 +210,7 @@ pub fn deinit(zig_object: *ZigObject, wasm: *Wasm) void { if (zig_object.dwarf) |*dwarf| { dwarf.deinit(); } - gpa.free(zig_object.path); + gpa.free(zig_object.path.sub_path); zig_object.* = undefined; } @@ -1236,6 +1236,7 @@ const codegen = @import("../../codegen.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.zig_object); const std = @import("std"); +const Path = std.Build.Cache.Path; const Air = @import("../../Air.zig"); const Atom = Wasm.Atom;