authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2024-02-12 17:12:53+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2024-02-29 15:24:07+01:00
log5ba5a2c133be5e06083f088aa875ff18658fbf8c
treefb79714cf6bbc5a519bbd7480a7ca32d6f2f4dc5
parent5ef832133895fd69fc8378463b86759eaab6913a
signaturelock-open Commit is signed but in an unrecognized format.

wasm: integrate linker errors with `Compilation`

Rather than using the logger, we now emit proper 'compiler'-errors just like the ELF and MachO linkers with notes. We now also support emitting multiple errors before quiting the linking process in certain phases, such as symbol resolution. This means we will print all symbols which were resolved incorrectly, rather than the first one we encounter.

4 files changed, 194 insertions(+), 106 deletions(-)

src/link/Wasm.zig+146-67
......@@ -655,9 +655,14 @@ fn parseObjectFile(wasm: *Wasm, path: []const u8) !bool {
655655 errdefer obj_file.close();
656656
657657 const gpa = wasm.base.comp.gpa;
658 var object = Object.create(gpa, obj_file, path, null) catch |err| switch (err) {
658 var object = Object.create(wasm, obj_file, path, null) catch |err| switch (err) {
659659 error.InvalidMagicByte, error.NotObjectFile => return false,
660 else => |e| return e,
660 else => |e| {
661 var err_note = try wasm.addErrorWithNotes(1);
662 try err_note.addMsg(wasm, "Failed parsing object file: {s}", .{@errorName(e)});
663 try err_note.addNote(wasm, "while parsing '{s}'", .{path});
664 return error.FlushFailure;
665 },
661666 };
662667 errdefer object.deinit(gpa);
663668 object.index = @enumFromInt(wasm.files.len);
......@@ -708,7 +713,12 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
708713 archive.deinit(gpa);
709714 return false;
710715 },
711 else => |e| return e,
716 else => |e| {
717 var err_note = try wasm.addErrorWithNotes(1);
718 try err_note.addMsg(wasm, "Failed parsing archive: {s}", .{@errorName(e)});
719 try err_note.addNote(wasm, "while parsing archive {s}", .{path});
720 return error.FlushFailure;
721 },
712722 };
713723
714724 if (!force_load) {
......@@ -730,7 +740,12 @@ fn parseArchive(wasm: *Wasm, path: []const u8, force_load: bool) !bool {
730740 }
731741
732742 for (offsets.keys()) |file_offset| {
733 var object = try archive.parseObject(gpa, file_offset);
743 var object = archive.parseObject(wasm, file_offset) catch |e| {
744 var err_note = try wasm.addErrorWithNotes(1);
745 try err_note.addMsg(wasm, "Failed parsing object: {s}", .{@errorName(e)});
746 try err_note.addNote(wasm, "while parsing object in archive {s}", .{path});
747 return error.FlushFailure;
748 };
734749 object.index = @enumFromInt(wasm.files.len);
735750 try wasm.files.append(gpa, .{ .object = object });
736751 try wasm.objects.append(gpa, object.index);
......@@ -764,9 +779,9 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void {
764779
765780 if (symbol.isLocal()) {
766781 if (symbol.isUndefined()) {
767 log.err("Local symbols are not allowed to reference imports", .{});
768 log.err(" symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() });
769 return error.UndefinedLocal;
782 var err = try wasm.addErrorWithNotes(1);
783 try err.addMsg(wasm, "Local symbols are not allowed to reference imports", .{});
784 try err.addNote(wasm, "symbol '{s}' defined in '{s}'", .{ sym_name, obj_file.path() });
770785 }
771786 try wasm.resolved_symbols.putNoClobber(gpa, location, {});
772787 continue;
......@@ -801,10 +816,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void {
801816 break :outer; // existing is weak, while new one isn't. Replace it.
802817 }
803818 // both are defined and weak, we have a symbol collision.
804 log.err("symbol '{s}' defined multiple times", .{sym_name});
805 log.err(" first definition in '{s}'", .{existing_file_path});
806 log.err(" next definition in '{s}'", .{obj_file.path()});
807 return error.SymbolCollision;
819 var err = try wasm.addErrorWithNotes(2);
820 try err.addMsg(wasm, "symbol '{s}' defined multiple times", .{sym_name});
821 try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path});
822 try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()});
808823 }
809824
810825 try wasm.discarded.put(gpa, location, existing_loc);
......@@ -812,10 +827,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void {
812827 }
813828
814829 if (symbol.tag != existing_sym.tag) {
815 log.err("symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) });
816 log.err(" first definition in '{s}'", .{existing_file_path});
817 log.err(" next definition in '{s}'", .{obj_file.path()});
818 return error.SymbolMismatchingType;
830 var err = try wasm.addErrorWithNotes(2);
831 try err.addMsg(wasm, "symbol '{s}' mismatching types '{s}' and '{s}'", .{ sym_name, @tagName(symbol.tag), @tagName(existing_sym.tag) });
832 try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path});
833 try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()});
819834 }
820835
821836 if (existing_sym.isUndefined() and symbol.isUndefined()) {
......@@ -832,14 +847,14 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void {
832847 const imp = obj_file.import(sym_index);
833848 const module_name = obj_file.string(imp.module_name);
834849 if (!mem.eql(u8, existing_name, module_name)) {
835 log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{
850 var err = try wasm.addErrorWithNotes(2);
851 try err.addMsg(wasm, "symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{
836852 sym_name,
837853 existing_name,
838854 module_name,
839855 });
840 log.err(" first definition in '{s}'", .{existing_file_path});
841 log.err(" next definition in '{s}'", .{obj_file.path()});
842 return error.ModuleNameMismatch;
856 try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path});
857 try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()});
843858 }
844859 }
845860
......@@ -852,10 +867,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void {
852867 const existing_ty = wasm.getGlobalType(existing_loc);
853868 const new_ty = wasm.getGlobalType(location);
854869 if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) {
855 log.err("symbol '{s}' mismatching global types", .{sym_name});
856 log.err(" first definition in '{s}'", .{existing_file_path});
857 log.err(" next definition in '{s}'", .{obj_file.path()});
858 return error.GlobalTypeMismatch;
870 var err = try wasm.addErrorWithNotes(2);
871 try err.addMsg(wasm, "symbol '{s}' mismatching global types", .{sym_name});
872 try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path});
873 try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()});
859874 }
860875 }
861876
......@@ -863,11 +878,11 @@ fn resolveSymbolsInObject(wasm: *Wasm, file_index: File.Index) !void {
863878 const existing_ty = wasm.getFunctionSignature(existing_loc);
864879 const new_ty = wasm.getFunctionSignature(location);
865880 if (!existing_ty.eql(new_ty)) {
866 log.err("symbol '{s}' mismatching function signatures.", .{sym_name});
867 log.err(" expected signature {}, but found signature {}", .{ existing_ty, new_ty });
868 log.err(" first definition in '{s}'", .{existing_file_path});
869 log.err(" next definition in '{s}'", .{obj_file.path()});
870 return error.FunctionSignatureMismatch;
881 var err = try wasm.addErrorWithNotes(3);
882 try err.addMsg(wasm, "symbol '{s}' mismatching function signatures.", .{sym_name});
883 try err.addNote(wasm, "expected signature {}, but found signature {}", .{ existing_ty, new_ty });
884 try err.addNote(wasm, "first definition in '{s}'", .{existing_file_path});
885 try err.addNote(wasm, "next definition in '{s}'", .{obj_file.path()});
871886 }
872887 }
873888
......@@ -914,7 +929,12 @@ fn resolveSymbolsInArchives(wasm: *Wasm) !void {
914929 // Symbol is found in unparsed object file within current archive.
915930 // Parse object and and resolve symbols again before we check remaining
916931 // undefined symbols.
917 var object = try archive.parseObject(gpa, offset.items[0]);
932 var object = archive.parseObject(wasm, offset.items[0]) catch |e| {
933 var err_note = try wasm.addErrorWithNotes(1);
934 try err_note.addMsg(wasm, "Failed parsing object: {s}", .{@errorName(e)});
935 try err_note.addNote(wasm, "while parsing object in archive {s}", .{archive.name});
936 return error.FlushFailure;
937 };
918938 object.index = @enumFromInt(wasm.files.len);
919939 try wasm.files.append(gpa, .{ .object = object });
920940 try wasm.objects.append(gpa, object.index);
......@@ -1214,20 +1234,21 @@ fn validateFeatures(
12141234 allowed[used_index] = is_enabled;
12151235 emit_features_count.* += @intFromBool(is_enabled);
12161236 } else if (is_enabled and !allowed[used_index]) {
1217 log.err("feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))});
1218 log.err(" defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path});
1237 var err = try wasm.addErrorWithNotes(1);
1238 try err.addMsg(wasm, "feature '{}' not allowed, but used by linked object", .{@as(types.Feature.Tag, @enumFromInt(used_index))});
1239 try err.addNote(wasm, "defined in '{s}'", .{wasm.files.items(.data)[used_set >> 1].object.path});
12191240 valid_feature_set = false;
12201241 }
12211242 }
12221243
12231244 if (!valid_feature_set) {
1224 return error.InvalidFeatureSet;
1245 return error.FlushFailure;
12251246 }
12261247
12271248 if (shared_memory) {
12281249 const disallowed_feature = disallowed[@intFromEnum(types.Feature.Tag.shared_mem)];
12291250 if (@as(u1, @truncate(disallowed_feature)) != 0) {
1230 log.err(
1251 try wasm.addErrorWithoutNotes(
12311252 "shared-memory is disallowed by '{s}' because it wasn't compiled with 'atomics' and 'bulk-memory' features enabled",
12321253 .{wasm.files.items(.data)[disallowed_feature >> 1].object.path},
12331254 );
......@@ -1236,7 +1257,7 @@ fn validateFeatures(
12361257
12371258 for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
12381259 if (!allowed[@intFromEnum(feature)]) {
1239 log.err("feature '{}' is not used but is required for shared-memory", .{feature});
1260 try wasm.addErrorWithoutNotes("feature '{}' is not used but is required for shared-memory", .{feature});
12401261 }
12411262 }
12421263 }
......@@ -1244,7 +1265,7 @@ fn validateFeatures(
12441265 if (has_tls) {
12451266 for ([_]types.Feature.Tag{ .atomics, .bulk_memory }) |feature| {
12461267 if (!allowed[@intFromEnum(feature)]) {
1247 log.err("feature '{}' is not used but is required for thread-local storage", .{feature});
1268 try wasm.addErrorWithoutNotes("feature '{}' is not used but is required for thread-local storage", .{feature});
12481269 }
12491270 }
12501271 }
......@@ -1257,9 +1278,10 @@ fn validateFeatures(
12571278 // from here a feature is always used
12581279 const disallowed_feature = disallowed[@intFromEnum(feature.tag)];
12591280 if (@as(u1, @truncate(disallowed_feature)) != 0) {
1260 log.err("feature '{}' is disallowed, but used by linked object", .{feature.tag});
1261 log.err(" disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path});
1262 log.err(" used in '{s}'", .{object.path});
1281 var err = try wasm.addErrorWithNotes(2);
1282 try err.addMsg(wasm, "feature '{}' is disallowed, but used by linked object", .{feature.tag});
1283 try err.addNote(wasm, "disallowed by '{s}'", .{wasm.files.items(.data)[disallowed_feature >> 1].object.path});
1284 try err.addNote(wasm, "used in '{s}'", .{object.path});
12631285 valid_feature_set = false;
12641286 }
12651287
......@@ -1270,16 +1292,17 @@ fn validateFeatures(
12701292 for (required, 0..) |required_feature, feature_index| {
12711293 const is_required = @as(u1, @truncate(required_feature)) != 0;
12721294 if (is_required and !object_used_features[feature_index]) {
1273 log.err("feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))});
1274 log.err(" required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path});
1275 log.err(" missing in '{s}'", .{object.path});
1295 var err = try wasm.addErrorWithNotes(2);
1296 try err.addMsg(wasm, "feature '{}' is required but not used in linked object", .{@as(types.Feature.Tag, @enumFromInt(feature_index))});
1297 try err.addNote(wasm, "required by '{s}'", .{wasm.files.items(.data)[required_feature >> 1].object.path});
1298 try err.addNote(wasm, "missing in '{s}'", .{object.path});
12761299 valid_feature_set = false;
12771300 }
12781301 }
12791302 }
12801303
12811304 if (!valid_feature_set) {
1282 return error.InvalidFeatureSet;
1305 return error.FlushFailure;
12831306 }
12841307
12851308 to_emit.* = allowed;
......@@ -1350,12 +1373,13 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
13501373 else
13511374 wasm.name;
13521375 const symbol_name = undef.getName(wasm);
1353 log.err("could not resolve undefined symbol '{s}'", .{symbol_name});
1354 log.err(" defined in '{s}'", .{file_name});
1376 var err = try wasm.addErrorWithNotes(1);
1377 try err.addMsg(wasm, "could not resolve undefined symbol '{s}'", .{symbol_name});
1378 try err.addNote(wasm, "defined in '{s}'", .{file_name});
13551379 }
13561380 }
13571381 if (found_undefined_symbols) {
1358 return error.UndefinedSymbol;
1382 return error.FlushFailure;
13591383 }
13601384}
13611385
......@@ -1728,8 +1752,7 @@ fn setupInitFunctions(wasm: *Wasm) !void {
17281752 break :ty object.func_types[func.type_index];
17291753 };
17301754 if (ty.params.len != 0) {
1731 log.err("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)});
1732 return error.InvalidInitFunc;
1755 try wasm.addErrorWithoutNotes("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)});
17331756 }
17341757 log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)});
17351758 wasm.init_funcs.appendAssumeCapacity(.{
......@@ -2108,7 +2131,7 @@ fn setupExports(wasm: *Wasm) !void {
21082131
21092132 for (force_exp_names) |exp_name| {
21102133 const loc = wasm.findGlobalSymbol(exp_name) orelse {
2111 log.err("could not export '{s}', symbol not found", .{exp_name});
2134 try wasm.addErrorWithoutNotes("could not export '{s}', symbol not found", .{exp_name});
21122135 failed_exports = true;
21132136 continue;
21142137 };
......@@ -2118,7 +2141,7 @@ fn setupExports(wasm: *Wasm) !void {
21182141 }
21192142
21202143 if (failed_exports) {
2121 return error.MissingSymbol;
2144 return error.FlushFailure;
21222145 }
21232146 }
21242147
......@@ -2164,14 +2187,14 @@ fn setupStart(wasm: *Wasm) !void {
21642187 const entry_name = wasm.entry_name orelse return;
21652188
21662189 const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {
2167 log.err("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name});
2168 return error.MissingSymbol;
2190 try wasm.addErrorWithoutNotes("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name});
2191 return error.FlushFailure;
21692192 };
21702193
21712194 const symbol = symbol_loc.getSymbol(wasm);
21722195 if (symbol.tag != .function) {
2173 log.err("Entry symbol '{s}' is not a function", .{entry_name});
2174 return error.InvalidEntryKind;
2196 try wasm.addErrorWithoutNotes("Entry symbol '{s}' is not a function", .{entry_name});
2197 return error.FlushFailure;
21752198 }
21762199
21772200 // Ensure the symbol is exported so host environment can access it
......@@ -2274,16 +2297,13 @@ fn setupMemory(wasm: *Wasm) !void {
22742297
22752298 if (wasm.initial_memory) |initial_memory| {
22762299 if (!std.mem.isAlignedGeneric(u64, initial_memory, page_size)) {
2277 log.err("Initial memory must be {d}-byte aligned", .{page_size});
2278 return error.MissAlignment;
2300 try wasm.addErrorWithoutNotes("Initial memory must be {d}-byte aligned", .{page_size});
22792301 }
22802302 if (memory_ptr > initial_memory) {
2281 log.err("Initial memory too small, must be at least {d} bytes", .{memory_ptr});
2282 return error.MemoryTooSmall;
2303 try wasm.addErrorWithoutNotes("Initial memory too small, must be at least {d} bytes", .{memory_ptr});
22832304 }
22842305 if (initial_memory > max_memory_allowed) {
2285 log.err("Initial memory exceeds maximum memory {d}", .{max_memory_allowed});
2286 return error.MemoryTooBig;
2306 try wasm.addErrorWithoutNotes("Initial memory exceeds maximum memory {d}", .{max_memory_allowed});
22872307 }
22882308 memory_ptr = initial_memory;
22892309 }
......@@ -2300,16 +2320,13 @@ fn setupMemory(wasm: *Wasm) !void {
23002320
23012321 if (wasm.max_memory) |max_memory| {
23022322 if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) {
2303 log.err("Maximum memory must be {d}-byte aligned", .{page_size});
2304 return error.MissAlignment;
2323 try wasm.addErrorWithoutNotes("Maximum memory must be {d}-byte aligned", .{page_size});
23052324 }
23062325 if (memory_ptr > max_memory) {
2307 log.err("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr});
2308 return error.MemoryTooSmall;
2326 try wasm.addErrorWithoutNotes("Maxmimum memory too small, must be at least {d} bytes", .{memory_ptr});
23092327 }
23102328 if (max_memory > max_memory_allowed) {
2311 log.err("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed});
2312 return error.MemoryTooBig;
2329 try wasm.addErrorWithoutNotes("Maximum memory exceeds maxmium amount {d}", .{max_memory_allowed});
23132330 }
23142331 wasm.memories.limits.max = @as(u32, @intCast(max_memory / page_size));
23152332 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
24122429 break :blk index;
24132430 };
24142431 } else {
2415 log.err("found unknown section '{s}'", .{section_name});
2432 var err = try wasm.addErrorWithNotes(1);
2433 try err.addMsg(wasm, "found unknown section '{s}'", .{section_name});
2434 try err.addNote(wasm, "defined in '{s}'", .{obj_file.path()});
24162435 return error.UnexpectedValue;
24172436 }
24182437 },
......@@ -2529,18 +2548,22 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node)
25292548 if (wasm.zig_object_index != .null) {
25302549 try wasm.resolveSymbolsInObject(wasm.zig_object_index);
25312550 }
2551 if (comp.link_errors.items.len > 0) return error.FlushFailure;
25322552 for (wasm.objects.items) |object_index| {
25332553 try wasm.resolveSymbolsInObject(object_index);
25342554 }
2555 if (comp.link_errors.items.len > 0) return error.FlushFailure;
25352556
25362557 var emit_features_count: u32 = 0;
25372558 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
25382559 try wasm.validateFeatures(&enabled_features, &emit_features_count);
25392560 try wasm.resolveSymbolsInArchives();
2561 if (comp.link_errors.items.len > 0) return error.FlushFailure;
25402562 try wasm.resolveLazySymbols();
25412563 try wasm.checkUndefinedSymbols();
25422564
25432565 try wasm.setupInitFunctions();
2566 if (comp.link_errors.items.len > 0) return error.FlushFailure;
25442567 try wasm.setupStart();
25452568
25462569 try wasm.markReferences();
......@@ -2549,6 +2572,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node)
25492572 try wasm.mergeTypes();
25502573 try wasm.allocateAtoms();
25512574 try wasm.setupMemory();
2575 if (comp.link_errors.items.len > 0) return error.FlushFailure;
25522576 wasm.allocateVirtualAddresses();
25532577 wasm.mapFunctionTable();
25542578 try wasm.initializeCallCtorsFunction();
......@@ -2558,6 +2582,7 @@ pub fn flushModule(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node)
25582582 try wasm.setupStartSection();
25592583 try wasm.setupExports();
25602584 try wasm.writeToFile(enabled_features, emit_features_count, arena);
2585 if (comp.link_errors.items.len > 0) return error.FlushFailure;
25612586}
25622587
25632588/// Writes the WebAssembly in-memory module to the file
......@@ -2955,7 +2980,7 @@ fn writeToFile(
29552980 }) catch unreachable;
29562981 try emitBuildIdSection(&binary_bytes, str);
29572982 },
2958 else => |mode| log.err("build-id '{s}' is not supported for WASM", .{@tagName(mode)}),
2983 else => |mode| try wasm.addErrorWithoutNotes("build-id '{s}' is not supported for WebAssembly", .{@tagName(mode)}),
29592984 }
29602985
29612986 var debug_bytes = std.ArrayList(u8).init(gpa);
......@@ -4043,3 +4068,57 @@ fn defaultEntrySymbolName(wasi_exec_model: std.builtin.WasiExecModel) []const u8
40434068 .command => "_start",
40444069 };
40454070}
4071
4072const ErrorWithNotes = struct {
4073 /// Allocated index in comp.link_errors array.
4074 index: usize,
4075
4076 /// Next available note slot.
4077 note_slot: usize = 0,
4078
4079 pub fn addMsg(
4080 err: ErrorWithNotes,
4081 wasm_file: *const Wasm,
4082 comptime format: []const u8,
4083 args: anytype,
4084 ) error{OutOfMemory}!void {
4085 const comp = wasm_file.base.comp;
4086 const gpa = comp.gpa;
4087 const err_msg = &comp.link_errors.items[err.index];
4088 err_msg.msg = try std.fmt.allocPrint(gpa, format, args);
4089 }
4090
4091 pub fn addNote(
4092 err: *ErrorWithNotes,
4093 wasm_file: *const Wasm,
4094 comptime format: []const u8,
4095 args: anytype,
4096 ) error{OutOfMemory}!void {
4097 const comp = wasm_file.base.comp;
4098 const gpa = comp.gpa;
4099 const err_msg = &comp.link_errors.items[err.index];
4100 err_msg.notes[err.note_slot] = .{ .msg = try std.fmt.allocPrint(gpa, format, args) };
4101 err.note_slot += 1;
4102 }
4103};
4104
4105pub fn addErrorWithNotes(wasm: *const Wasm, note_count: usize) error{OutOfMemory}!ErrorWithNotes {
4106 const comp = wasm.base.comp;
4107 const gpa = comp.gpa;
4108 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
4109 return wasm.addErrorWithNotesAssumeCapacity(note_count);
4110}
4111
4112pub fn addErrorWithoutNotes(wasm: *const Wasm, comptime fmt: []const u8, args: anytype) !void {
4113 const err = try wasm.addErrorWithNotes(0);
4114 try err.addMsg(wasm, fmt, args);
4115}
4116
4117fn addErrorWithNotesAssumeCapacity(wasm: *const Wasm, note_count: usize) error{OutOfMemory}!ErrorWithNotes {
4118 const comp = wasm.base.comp;
4119 const gpa = comp.gpa;
4120 const index = comp.link_errors.items.len;
4121 const err = comp.link_errors.addOneAssumeCapacity();
4122 err.* = .{ .msg = undefined, .notes = try gpa.alloc(link.File.ErrorMsg, note_count) };
4123 return .{ .index = index };
4124}
src/link/Wasm/Archive.zig+21-25
......@@ -1,14 +1,3 @@
1const Archive = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const fs = std.fs;
6const log = std.log.scoped(.archive);
7const mem = std.mem;
8
9const Allocator = mem.Allocator;
10const Object = @import("Object.zig");
11
121file: fs.File,
132name: []const u8,
143
......@@ -151,10 +140,7 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype
151140 const sym_tab = try allocator.alloc(u8, sym_tab_size - 4 - (4 * num_symbols));
152141 defer allocator.free(sym_tab);
153142
154 reader.readNoEof(sym_tab) catch {
155 log.err("incomplete symbol table: expected symbol table of length 0x{x}", .{sym_tab.len});
156 return error.MalformedArchive;
157 };
143 reader.readNoEof(sym_tab) catch return error.IncompleteSymbolTable;
158144
159145 var i: usize = 0;
160146 var pos: usize = 0;
......@@ -178,12 +164,10 @@ fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype
178164fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void {
179165 const header: ar_hdr = try reader.readStruct(ar_hdr);
180166 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
181 log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag });
182 return error.MalformedArchive;
167 return error.InvalidHeaderDelimiter;
183168 }
184169 if (!mem.eql(u8, header.ar_name[0..2], "//")) {
185 log.err("invalid archive. Long name table missing", .{});
186 return error.MalformedArchive;
170 return error.MissingTableName;
187171 }
188172 const table_size = try header.size();
189173 const long_file_names = try allocator.alloc(u8, table_size);
......@@ -194,7 +178,8 @@ fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !voi
194178
195179/// From a given file offset, starts reading for a file header.
196180/// When found, parses the object file into an `Object` and returns it.
197pub fn parseObject(archive: Archive, allocator: Allocator, file_offset: u32) !Object {
181pub fn parseObject(archive: Archive, wasm_file: *const Wasm, file_offset: u32) !Object {
182 const gpa = wasm_file.base.comp.gpa;
198183 try archive.file.seekTo(file_offset);
199184 const reader = archive.file.reader();
200185 const header = try reader.readStruct(ar_hdr);
......@@ -202,22 +187,33 @@ pub fn parseObject(archive: Archive, allocator: Allocator, file_offset: u32) !Ob
202187 try archive.file.seekTo(0);
203188
204189 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
205 log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag });
206 return error.MalformedArchive;
190 return error.InvalidHeaderDelimiter;
207191 }
208192
209193 const object_name = try archive.parseName(header);
210194 const name = name: {
211195 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
212196 const path = try std.os.realpath(archive.name, &buffer);
213 break :name try std.fmt.allocPrint(allocator, "{s}({s})", .{ path, object_name });
197 break :name try std.fmt.allocPrint(gpa, "{s}({s})", .{ path, object_name });
214198 };
215 defer allocator.free(name);
199 defer gpa.free(name);
216200
217201 const object_file = try std.fs.cwd().openFile(archive.name, .{});
218202 errdefer object_file.close();
219203
220204 const object_file_size = try header.size();
221205 try object_file.seekTo(current_offset);
222 return Object.create(allocator, object_file, name, object_file_size);
206 return Object.create(wasm_file, object_file, name, object_file_size);
223207}
208
209const std = @import("std");
210const assert = std.debug.assert;
211const fs = std.fs;
212const log = std.log.scoped(.archive);
213const mem = std.mem;
214
215const Allocator = mem.Allocator;
216const Object = @import("Object.zig");
217const Wasm = @import("../Wasm.zig");
218
219const Archive = @This();
src/link/Wasm/Object.zig+26-13
......@@ -127,7 +127,8 @@ pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadErro
127127/// This also parses and verifies the object file.
128128/// When a max size is given, will only parse up to the given size,
129129/// else will read until the end of the file.
130pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object {
130pub fn create(wasm_file: *const Wasm, file: std.fs.File, name: []const u8, maybe_max_size: ?usize) InitError!Object {
131 const gpa = wasm_file.base.comp.gpa;
131132 var object: Object = .{
132133 .file = file,
133134 .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
151152 }
152153 var fbs = std.io.fixedBufferStream(file_contents);
153154
154 try object.parse(gpa, fbs.reader(), &is_object_file);
155 try object.parse(gpa, wasm_file, fbs.reader(), &is_object_file);
155156 errdefer object.deinit(gpa);
156157 if (!is_object_file) return error.NotObjectFile;
157158
......@@ -224,7 +225,7 @@ pub fn findImport(object: *const Object, sym: Symbol) types.Import {
224225/// we initialize a new table symbol that corresponds to that import and return that symbol.
225226///
226227/// When the object file is *NOT* MVP, we return `null`.
227fn checkLegacyIndirectFunctionTable(object: *Object) !?Symbol {
228fn checkLegacyIndirectFunctionTable(object: *Object, wasm_file: *const Wasm) !?Symbol {
228229 var table_count: usize = 0;
229230 for (object.symtable) |sym| {
230231 if (sym.tag == .table) table_count += 1;
......@@ -234,21 +235,27 @@ fn checkLegacyIndirectFunctionTable(object: *Object) !?Symbol {
234235 if (object.imported_tables_count == table_count) return null;
235236
236237 if (table_count != 0) {
237 log.err("Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
238 var err = try wasm_file.addErrorWithNotes(1);
239 try err.addMsg(wasm_file, "Expected a table entry symbol for each of the {d} table(s), but instead got {d} symbols.", .{
238240 object.imported_tables_count,
239241 table_count,
240242 });
243 try err.addNote(wasm_file, "defined in '{s}'", .{object.path});
241244 return error.MissingTableSymbols;
242245 }
243246
244247 // MVP object files cannot have any table definitions, only imports (for the indirect function table).
245248 if (object.tables.len > 0) {
246 log.err("Unexpected table definition without representing table symbols.", .{});
249 var err = try wasm_file.addErrorWithNotes(1);
250 try err.addMsg(wasm_file, "Unexpected table definition without representing table symbols.", .{});
251 try err.addNote(wasm_file, "defined in '{s}'", .{object.path});
247252 return error.UnexpectedTable;
248253 }
249254
250255 if (object.imported_tables_count != 1) {
251 log.err("Found more than one table import, but no representing table symbols", .{});
256 var err = try wasm_file.addErrorWithNotes(1);
257 try err.addMsg(wasm_file, "Found more than one table import, but no representing table symbols", .{});
258 try err.addNote(wasm_file, "defined in '{s}'", .{object.path});
252259 return error.MissingTableSymbols;
253260 }
254261
......@@ -259,7 +266,9 @@ fn checkLegacyIndirectFunctionTable(object: *Object) !?Symbol {
259266 } else unreachable;
260267
261268 if (!std.mem.eql(u8, object.string_table.get(table_import.name), "__indirect_function_table")) {
262 log.err("Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)});
269 var err = try wasm_file.addErrorWithNotes(1);
270 try err.addMsg(wasm_file, "Non-indirect function table import '{s}' is missing a corresponding symbol", .{object.string_table.get(table_import.name)});
271 try err.addNote(wasm_file, "defined in '{s}'", .{object.path});
263272 return error.MissingTableSymbols;
264273 }
265274
......@@ -312,8 +321,8 @@ pub const ParseError = error{
312321 UnknownFeature,
313322};
314323
315fn parse(object: *Object, gpa: Allocator, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void {
316 var parser = Parser(@TypeOf(reader)).init(object, reader);
324fn parse(object: *Object, gpa: Allocator, wasm_file: *const Wasm, reader: anytype, is_object_file: *bool) Parser(@TypeOf(reader)).Error!void {
325 var parser = Parser(@TypeOf(reader)).init(object, wasm_file, reader);
317326 return parser.parseObject(gpa, is_object_file);
318327}
319328
......@@ -325,9 +334,11 @@ fn Parser(comptime ReaderType: type) type {
325334 reader: std.io.CountingReader(ReaderType),
326335 /// Object file we're building
327336 object: *Object,
337 /// Read-only reference to the WebAssembly linker
338 wasm_file: *const Wasm,
328339
329 fn init(object: *Object, reader: ReaderType) ObjectParser {
330 return .{ .object = object, .reader = std.io.countingReader(reader) };
340 fn init(object: *Object, wasm_file: *const Wasm, reader: ReaderType) ObjectParser {
341 return .{ .object = object, .wasm_file = wasm_file, .reader = std.io.countingReader(reader) };
331342 }
332343
333344 /// Verifies that the first 4 bytes contains \0Asm
......@@ -585,7 +596,9 @@ fn Parser(comptime ReaderType: type) type {
585596 try reader.readNoEof(name);
586597
587598 const tag = types.known_features.get(name) orelse {
588 log.err("Object file contains unknown feature: {s}", .{name});
599 var err = try parser.wasm_file.addErrorWithNotes(1);
600 try err.addMsg(parser.wasm_file, "Object file contains unknown feature: {s}", .{name});
601 try err.addNote(parser.wasm_file, "defined in '{s}'", .{parser.object.path});
589602 return error.UnknownFeature;
590603 };
591604 feature.* = .{
......@@ -754,7 +767,7 @@ fn Parser(comptime ReaderType: type) type {
754767
755768 // we found all symbols, check for indirect function table
756769 // in case of an MVP object file
757 if (try parser.object.checkLegacyIndirectFunctionTable()) |symbol| {
770 if (try parser.object.checkLegacyIndirectFunctionTable(parser.wasm_file)) |symbol| {
758771 try symbols.append(symbol);
759772 log.debug("Found legacy indirect function table. Created symbol", .{});
760773 }
src/link/Wasm/ZigObject.zig+1-1
......@@ -1213,6 +1213,6 @@ const StringTable = @import("../StringTable.zig");
12131213const Symbol = @import("Symbol.zig");
12141214const Type = @import("../../type.zig").Type;
12151215const TypedValue = @import("../../TypedValue.zig");
1216const Value = @import("../../value.zig").Value;
1216const Value = @import("../../Value.zig");
12171217const Wasm = @import("../Wasm.zig");
12181218const ZigObject = @This();