diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index ce5f451a6f98aeb6c82b0b1a44d5de188a303639..db0e275810dacd9de0c925e64da6ddc38856cb0d 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -655,9 +655,14 @@ fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool { errdefer obj_file.close(); const gpa = wasm.base.comp.gpa; - var object = Object.create(gpa, obj_file, path, null) catch |err| switch (err) { + var object = Object.create(wasm, obj_file, path, null) catch |err| switch (err) { error.InvalidMagicByte, error.NotObjectFile => return false, - else => |e| return e, + else => |e| { + var err_note = try wasm.addErrorWithNotes(1); + try err_note.addMsg(wasm, "Failed parsing object file: {s}", .{@errorName(e)}); + try err_note.addNote(wasm, "while parsing '{s}'", .{path}); + return error.FlushFailure; + }, }; errdefer object.deinit(gpa); object.index = @enumFromInt(wasm.files.len); @@ -708,7 +713,12 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { archive.deinit(gpa); return false; }, - else => |e| return e, + else => |e| { + var err_note = try wasm.addErrorWithNotes(1); + try err_note.addMsg(wasm, "Failed parsing archive: {s}", .{@errorName(e)}); + try err_note.addNote(wasm, "while parsing archive {s}", .{path}); + return error.FlushFailure; + }, }; if (!force_load) { @@ -730,7 +740,12 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool { } for (offsets.keys()) |file_offset| { - var object = try archive.parseObject(gpa, file_offset); + var object = archive.parseObject(wasm, file_offset) catch |e| { + var err_note = try wasm.addErrorWithNotes(1); + try err_note.addMsg(wasm, "Failed parsing object: {s}", .{@errorName(e)}); + try err_note.addNote(wasm, "while parsing object in archive {s}", .{path}); + return error.FlushFailure; + }; object.index = @enumFromInt(wasm.files.len); try wasm.files.append(gpa, .{ .object = object }); try wasm.objects.append(gpa, object.index); @@ -764,9 +779,9 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { if (symbol.isLocal()) { if (symbol.isUndefined()) { - log.err("Local symbols are not allowed to reference imports", .{}); - log.err(" symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() }); - return error.UndefinedLocal; + var err = try wasm.addErrorWithNotes(1); + try err.addMsg(wasm, "Local symbols are not allowed to reference imports", .{}); + try err.addNote(wasm, "symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() }); } try wasm.resolved_symbols.putNoClobber(gpa, location, {}); continue; @@ -801,10 +816,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { break :outer; // existing is weak, while new one isn't. Replace it. } // both are defined and weak, we have a symbol collision. - log.err("symbol '{s}' defined multiple times", .{sym_name}); - log.err(" first definition in '{s}'", .{existing_file_path}); - log.err(" next definition in '{s}'", .{obj_file.path()}); - return error.SymbolCollision; + var err = try wasm.addErrorWithNotes(2); + try err.addMsg(wasm, "symbol '{s}' defined multiple times", .{sym_name}); + try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); + try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); } try wasm.discarded.put(gpa, location, existing_loc); @@ -812,10 +827,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { } if (symbol.tag != existing_sym.tag) { - log.err("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); - log.err(" first definition in '{s}'", .{existing_file_path}); - log.err(" next definition in '{s}'", .{obj_file.path()}); - return error.SymbolMismatchingType; + var err = try wasm.addErrorWithNotes(2); + try err.addMsg(wasm, "symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) }); + try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); + try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); } if (existing_sym.isUndefined() and symbol.isUndefined()) { @@ -832,14 +847,14 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { const imp = obj_file.import(sym_index); const module_name = obj_file.string(imp.module_name); if (!mem.eql(u8, existing_name, module_name)) { - log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ + var err = try wasm.addErrorWithNotes(2); + try err.addMsg(wasm, "symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{ sym_name, existing_name, module_name, }); - log.err(" first definition in '{s}'", .{existing_file_path}); - log.err(" next definition in '{s}'", .{obj_file.path()}); - return error.ModuleNameMismatch; + try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); + try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); } } @@ -852,10 +867,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { const existing_ty = wasm.getGlobalType(existing_loc); const new_ty = wasm.getGlobalType(location); if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) { - log.err("symbol '{s}' mismatching global types", .{sym_name}); - log.err(" first definition in '{s}'", .{existing_file_path}); - log.err(" next definition in '{s}'", .{obj_file.path()}); - return error.GlobalTypeMismatch; + var err = try wasm.addErrorWithNotes(2); + try err.addMsg(wasm, "symbol '{s}' mismatching global types", .{sym_name}); + try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); + try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); } } @@ -863,11 +878,11 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void { const existing_ty = wasm.getFunctionSignature(existing_loc); const new_ty = wasm.getFunctionSignature(location); if (!existing_ty.eql(new_ty)) { - log.err("symbol '{s}' mismatching function signatures.", .{sym_name}); - log.err(" expected signature {}, but found signature {}", .{ existing_ty, new_ty }); - log.err(" first definition in '{s}'", .{existing_file_path}); - log.err(" next definition in '{s}'", .{obj_file.path()}); - return error.FunctionSignatureMismatch; + var err = try wasm.addErrorWithNotes(3); + try err.addMsg(wasm, "symbol '{s}' mismatching function signatures.", .{sym_name}); + try err.addNote(wasm, "expected signature {}, but found signature {}", .{ existing_ty, new_ty }); + try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path}); + try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()}); } } @@ -914,7 +929,12 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void { // Symbol is found in unparsed object file within current archive. // Parse object and and resolve symbols again before we check remaining // undefined symbols. - var object = try archive.parseObject(gpa, offset.items[0]); + var object = archive.parseObject(wasm, offset.items[0]) catch |e| { + var err_note = try wasm.addErrorWithNotes(1); + try err_note.addMsg(wasm, "Failed parsing object: {s}", .{@errorName(e)}); + try err_note.addNote(wasm, "while parsing object in archive {s}", .{archive.name}); + return error.FlushFailure; + }; object.index = @enumFromInt(wasm.files.len); try wasm.files.append(gpa, .{ .object = object }); try wasm.objects.append(gpa, object.index); @@ -1214,20 +1234,21 @@ fn validateFeatures( allowed[used_index] = is_enabled; emit_features_count.* += @intFromBool(is_enabled); } else if (is_enabled and !allowed[used_index]) { - log.err("feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))}); - log.err(" defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path}); + var err = try wasm.addErrorWithNotes(1); + try err.addMsg(wasm, "feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))}); + try err.addNote(wasm, "defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path}); valid_feature_set = false; } } if (!valid_feature_set) { - return error.InvalidFeatureSet; + return error.FlushFailure; } if (shared_memory) { const disallowed_feature = disallowed[@intFromEnum(types.Feature.Tag.shared_mem)]; if (@as(u1, @truncate(disallowed_feature)) != 0) { - log.err( + try wasm.addErrorWithoutNotes( "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}, ); @@ -1236,7 +1257,7 @@ fn validateFeatures( for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| { if (!allowed[@intFromEnum(feature)]) { - log.err("feature '{}' is not used but is required for shared-memory", .{feature}); + try wasm.addErrorWithoutNotes("feature '{}' is not used but is required for shared-memory", .{feature}); } } } @@ -1244,7 +1265,7 @@ fn validateFeatures( if (has_tls) { for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| { if (!allowed[@intFromEnum(feature)]) { - log.err("feature '{}' is not used but is required for thread-local storage", .{feature}); + try wasm.addErrorWithoutNotes("feature '{}' is not used but is required for thread-local storage", .{feature}); } } } @@ -1257,9 +1278,10 @@ fn validateFeatures( // from here a feature is always used const disallowed_feature = disallowed[@intFromEnum(feature.tag)]; if (@as(u1, @truncate(disallowed_feature)) != 0) { - log.err("feature '{}' is disallowed, but used by linked object", .{feature.tag}); - log.err(" disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}); - log.err(" used in '{s}'", .{object.path}); + var err = try wasm.addErrorWithNotes(2); + try err.addMsg(wasm, "feature '{}' is disallowed, but used by linked object", .{feature.tag}); + try err.addNote(wasm, "disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path}); + try err.addNote(wasm, "used in '{s}'", .{object.path}); valid_feature_set = false; } @@ -1270,16 +1292,17 @@ fn validateFeatures( for (required, 0..) |required_feature, feature_index| { const is_required = @as(u1, @truncate(required_feature)) != 0; if (is_required and !object_used_features[feature_index]) { - log.err("feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))}); - log.err(" required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path}); - log.err(" missing in '{s}'", .{object.path}); + var err = try wasm.addErrorWithNotes(2); + try err.addMsg(wasm, "feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))}); + try err.addNote(wasm, "required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path}); + try err.addNote(wasm, "missing in '{s}'", .{object.path}); valid_feature_set = false; } } } if (!valid_feature_set) { - return error.InvalidFeatureSet; + return error.FlushFailure; } to_emit.* = allowed; @@ -1350,12 +1373,13 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void { else wasm.name; const symbol_name = undef.getName(wasm); - log.err("could not resolve undefined symbol '{s}'", .{symbol_name}); - log.err(" defined in '{s}'", .{file_name}); + var err = try wasm.addErrorWithNotes(1); + try err.addMsg(wasm, "could not resolve undefined symbol '{s}'", .{symbol_name}); + try err.addNote(wasm, "defined in '{s}'", .{file_name}); } } if (found_undefined_symbols) { - return error.UndefinedSymbol; + return error.FlushFailure; } } @@ -1728,8 +1752,7 @@ fn setupInitFunctions(wasm: *Wasm) !void { break :ty object.func_types[func.type_index]; }; if (ty.params.len != 0) { - log.err("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)}); - return error.InvalidInitFunc; + try wasm.addErrorWithoutNotes("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)}); } log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)}); wasm.init_funcs.appendAssumeCapacity(.{ @@ -2108,7 +2131,7 @@ fn setupExports(wasm: *Wasm) !void { for (force_exp_names) |exp_name| { const loc = wasm.findGlobalSymbol(exp_name) orelse { - log.err("could not export '{s}', symbol not found", .{exp_name}); + try wasm.addErrorWithoutNotes("could not export '{s}', symbol not found", .{exp_name}); failed_exports = true; continue; }; @@ -2118,7 +2141,7 @@ fn setupExports(wasm: *Wasm) !void { } if (failed_exports) { - return error.MissingSymbol; + return error.FlushFailure; } } @@ -2164,14 +2187,14 @@ fn setupStart(wasm: *Wasm) !void { const entry_name = wasm.entry_name orelse return; const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse { - log.err("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name}); - return error.MissingSymbol; + try wasm.addErrorWithoutNotes("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name}); + return error.FlushFailure; }; const symbol = symbol_loc.getSymbol(wasm); if (symbol.tag != .function) { - log.err("Entry symbol '{s}' is not a function", .{entry_name}); - return error.InvalidEntryKind; + try wasm.addErrorWithoutNotes("Entry symbol '{s}' is not a function", .{entry_name}); + return error.FlushFailure; } // Ensure the symbol is exported so host environment can access it @@ -2274,16 +2297,13 @@ fn setupMemory(wasm: *Wasm) !void { if (wasm.initial_memory) |initial_memory| { if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) { - log.err("Initial memory must be {d}-byte aligned", .{page_size}); - return error.MissAlignment; + try wasm.addErrorWithoutNotes("Initial memory must be {d}-byte aligned", .{page_size}); } if (memory_ptr > initial_memory) { - log.err("Initial memory too small, must be at least {d} bytes", .{memory_ptr}); - return error.MemoryTooSmall; + try wasm.addErrorWithoutNotes("Initial memory too small, must be at least {d} bytes", .{memory_ptr}); } if (initial_memory > max_memory_allowed) { - log.err("Initial memory exceeds maximum memory {d}", .{max_memory_allowed}); - return error.MemoryTooBig; + try wasm.addErrorWithoutNotes("Initial memory exceeds maximum memory {d}", .{max_memory_allowed}); } memory_ptr = initial_memory; } @@ -2300,16 +2320,13 @@ fn setupMemory(wasm: *Wasm) !void { if (wasm.max_memory) |max_memory| { if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) { - log.err("Maximum memory must be {d}-byte aligned", .{page_size}); - return error.MissAlignment; + try wasm.addErrorWithoutNotes("Maximum memory must be {d}-byte aligned", .{page_size}); } if (memory_ptr > max_memory) { - log.err("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr}); - return error.MemoryTooSmall; + try wasm.addErrorWithoutNotes("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr}); } if (max_memory > max_memory_allowed) { - log.err("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed}); - return error.MemoryTooBig; + try wasm.addErrorWithoutNotes("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed}); } wasm.memories.limits.max = @as(u32, @intCast(max_memory / page_size)); wasm.memories.limits.setFlag(.WASM_LIMITS_FLAG_HAS_MAX); @@ -2412,7 +2429,9 @@ pub fn getMatchingSegment(wasm: *Wasm, file_index: File.Index, symbol_index: Sym break :blk index; }; } else { - log.err("found unknown section '{s}'", .{section_name}); + var err = try wasm.addErrorWithNotes(1); + try err.addMsg(wasm, "found unknown section '{s}'", .{section_name}); + try err.addNote(wasm, "defined in '{s}'", .{obj_file.path()}); return error.UnexpectedValue; } }, @@ -2529,18 +2548,22 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) if (wasm.zig_object_index != .null) { try wasm.resolveSymbolsInObject(wasm.zig_object_index); } + if (comp.link_errors.items.len > 0) return error.FlushFailure; for (wasm.objects.items) |object_index| { try wasm.resolveSymbolsInObject(object_index); } + if (comp.link_errors.items.len > 0) return error.FlushFailure; var emit_features_count: u32 = 0; var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined; try wasm.validateFeatures(&enabled_features, &emit_features_count); try wasm.resolveSymbolsInArchives(); + if (comp.link_errors.items.len > 0) return error.FlushFailure; try wasm.resolveLazySymbols(); try wasm.checkUndefinedSymbols(); try wasm.setupInitFunctions(); + if (comp.link_errors.items.len > 0) return error.FlushFailure; try wasm.setupStart(); try wasm.markReferences(); @@ -2549,6 +2572,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) try wasm.mergeTypes(); try wasm.allocateAtoms(); try wasm.setupMemory(); + if (comp.link_errors.items.len > 0) return error.FlushFailure; wasm.allocateVirtualAddresses(); wasm.mapFunctionTable(); try wasm.initializeCallCtorsFunction(); @@ -2558,6 +2582,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) try wasm.setupStartSection(); try wasm.setupExports(); try wasm.writeToFile(enabled_features, emit_features_count, arena); + if (comp.link_errors.items.len > 0) return error.FlushFailure; } /// Writes the WebAssembly in-memory module to the file @@ -2955,7 +2980,7 @@ fn writeToFile( }) catch unreachable; try emitBuildIdSection(&binary_bytes, str); }, - else => |mode| log.err("build-id '{s}' is not supported for WASM", .{@tagName(mode)}), + else => |mode| try wasm.addErrorWithoutNotes("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}), } var debug_bytes = std.ArrayList(u8).init(gpa); @@ -4043,3 +4068,57 @@ fn defaultEntrySymbolName(wasi_exec_model: std.builtin.WasiExecModel) []const u8 .command => "_start", }; } + +const ErrorWithNotes = struct { + /// Allocated index in comp.link_errors array. + index: usize, + + /// Next available note slot. + note_slot: usize = 0, + + pub fn addMsg( + err: ErrorWithNotes, + wasm_file: *const Wasm, + comptime format: []const u8, + args: anytype, + ) error{OutOfMemory}!void { + const comp = wasm_file.base.comp; + const gpa = comp.gpa; + const err_msg = &comp.link_errors.items[err.index]; + err_msg.msg = try std.fmt.allocPrint(gpa, format, args); + } + + pub fn addNote( + err: *ErrorWithNotes, + wasm_file: *const Wasm, + comptime format: []const u8, + args: anytype, + ) error{OutOfMemory}!void { + const comp = wasm_file.base.comp; + const gpa = comp.gpa; + const err_msg = &comp.link_errors.items[err.index]; + err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) }; + err.note_slot += 1; + } +}; + +pub fn addErrorWithNotes(wasm: *const Wasm, note_count: usize) error{OutOfMemory}!ErrorWithNotes { + const comp = wasm.base.comp; + const gpa = comp.gpa; + try comp.link_errors.ensureUnusedCapacity(gpa, 1); + return wasm.addErrorWithNotesAssumeCapacity(note_count); +} + +pub fn addErrorWithoutNotes(wasm: *const Wasm, comptime fmt: []const u8, args: anytype) !void { + const err = try wasm.addErrorWithNotes(0); + try err.addMsg(wasm, fmt, args); +} + +fn addErrorWithNotesAssumeCapacity(wasm: *const Wasm, note_count: usize) error{OutOfMemory}!ErrorWithNotes { + const comp = wasm.base.comp; + const gpa = comp.gpa; + const index = comp.link_errors.items.len; + const err = comp.link_errors.addOneAssumeCapacity(); + err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) }; + return .{ .index = index }; +} diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig index a618aaebbf31baa555f114907809f5a5953c8f39..028ef95726eec61321feb4c03f4d4478b9ff0a32 100644 --- a/src/link/Wasm/Archive.zig +++ b/src/link/Wasm/Archive.zig @@ -1,14 +1,3 @@ -const Archive = @This(); - -const std = @import("std"); -const assert = std.debug.assert; -const fs = std.fs; -const log = std.log.scoped(.archive); -const mem = std.mem; - -const Allocator = mem.Allocator; -const Object = @import("Object.zig"); - file: fs.File, name: []const u8, @@ -151,10 +140,7 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype const sym_tab = try allocator.alloc(u8, sym_tab_size - 4 - (4 * num_symbols)); defer allocator.free(sym_tab); - reader.readNoEof(sym_tab) catch { - log.err("incomplete symbol table: expected symbol table of length 0x{x}", .{sym_tab.len}); - return error.MalformedArchive; - }; + reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable; var i: usize = 0; var pos: usize = 0; @@ -178,12 +164,10 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void { const header: ar_hdr = try reader.readStruct(ar_hdr); if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) { - log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag }); - return error.MalformedArchive; + return error.InvalidHeaderDelimiter; } if (!mem.eql(u8, header.ar_name[0..2], "//")) { - log.err("invalid archive. Long name table missing", .{}); - return error.MalformedArchive; + return error.MissingTableName; } const table_size = try header.size(); const long_file_names = try allocator.alloc(u8, table_size); @@ -194,7 +178,8 @@ fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !voi /// 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, allocator: Allocator, file_offset: u32) !Object { +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); @@ -202,22 +187,33 @@ pub fn parseObject(archive: Archive, allocator: Allocator, file_offset: u32) !Ob try archive.file.seekTo(0); if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) { - log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag }); - return error.MalformedArchive; + return error.InvalidHeaderDelimiter; } const object_name = try archive.parseName(header); const name = name: { var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; const path = try std.os.realpath(archive.name, &buffer); - break :name try std.fmt.allocPrint(allocator, "{s}({s})", .{ path, object_name }); + break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name }); }; - defer allocator.free(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(allocator, object_file, name, object_file_size); + return Object.create(wasm_file, object_file, name, object_file_size); } + +const std = @import("std"); +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.archive); +const mem = std.mem; + +const Allocator = mem.Allocator; +const Object = @import("Object.zig"); +const Wasm = @import("../Wasm.zig"); + +const Archive = @This(); diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index 297e71991dc571af328eaea34b93179954d2f3ba..ca9f746d51e053250bba8e1e397f799e58d8163d 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -127,7 +127,8 @@ pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadErro /// 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(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object { +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; var object: Object = .{ .file = file, .path = try gpa.dupe(u8, name), @@ -151,7 +152,7 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz } var fbs = std.io.fixedBufferStream(file_contents); - try object.parse(gpa, fbs.reader(), &is_object_file); + try object.parse(gpa, wasm_file, fbs.reader(), &is_object_file); errdefer object.deinit(gpa); if (!is_object_file) return error.NotObjectFile; @@ -224,7 +225,7 @@ pub fn findImport(object: *const Object, sym: Symbol) types.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) !?Symbol { +fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?Symbol { var table_count: usize = 0; for (object.symtable) |sym| { if (sym.tag == .table) table_count += 1; @@ -234,21 +235,27 @@ fn checkLegacyIndirectFunctionTable(object: *Object) !?Symbol { if (object.imported_tables_count == table_count) return null; if (table_count != 0) { - log.err("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{ + var err = try wasm_file.addErrorWithNotes(1); + try err.addMsg(wasm_file, "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(wasm_file, "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) { - log.err("Unexpected table definition without representing table symbols.", .{}); + var err = try wasm_file.addErrorWithNotes(1); + try err.addMsg(wasm_file, "Unexpected table definition without representing table symbols.", .{}); + try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); return error.UnexpectedTable; } if (object.imported_tables_count != 1) { - log.err("Found more than one table import, but no representing table symbols", .{}); + var err = try wasm_file.addErrorWithNotes(1); + try err.addMsg(wasm_file, "Found more than one table import, but no representing table symbols", .{}); + try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); return error.MissingTableSymbols; } @@ -259,7 +266,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object) !?Symbol { } else unreachable; if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) { - log.err("Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)}); + var err = try wasm_file.addErrorWithNotes(1); + try err.addMsg(wasm_file, "Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)}); + try err.addNote(wasm_file, "defined in '{s}'", .{object.path}); return error.MissingTableSymbols; } @@ -312,8 +321,8 @@ pub const ParseError = error{ UnknownFeature, }; -fn parse(object: *Object, gpa: Allocator, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void { - var parser = Parser(@TypeOf(reader)).init(object, reader); +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); } @@ -325,9 +334,11 @@ fn Parser(comptime ReaderType: type) type { 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, reader: ReaderType) ObjectParser { - return .{ .object = object, .reader = std.io.countingReader(reader) }; + 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 @@ -585,7 +596,9 @@ fn Parser(comptime ReaderType: type) type { try reader.readNoEof(name); const tag = types.known_features.get(name) orelse { - log.err("Object file contains unknown feature: {s}", .{name}); + var err = try parser.wasm_file.addErrorWithNotes(1); + try err.addMsg(parser.wasm_file, "Object file contains unknown feature: {s}", .{name}); + try err.addNote(parser.wasm_file, "defined in '{s}'", .{parser.object.path}); return error.UnknownFeature; }; feature.* = .{ @@ -754,7 +767,7 @@ fn Parser(comptime ReaderType: type) type { // we found all symbols, check for indirect function table // in case of an MVP object file - if (try parser.object.checkLegacyIndirectFunctionTable()) |symbol| { + if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm_file)) |symbol| { try symbols.append(symbol); log.debug("Found legacy indirect function table. Created symbol", .{}); } diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index d47bb6b72159c63d09d9ec4824087ec26c5a8c7d..9cb3e82c9067be92a1d020bae090bc99dd688f3b 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -1213,6 +1213,6 @@ const StringTable = @import("../StringTable.zig"); const Symbol = @import("Symbol.zig"); const Type = @import("../../type.zig").Type; const TypedValue = @import("../../TypedValue.zig"); -const Value = @import("../../value.zig").Value; +const Value = @import("../../Value.zig"); const Wasm = @import("../Wasm.zig"); const ZigObject = @This();