authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-08-16 20:50:50+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-08-18 14:17:01+02:00
log4f2143beccb822f42c77d1d6c5ad31388123803e
treef17629e9875ad5086ec7e90820096a3746764ca9
parent63c25cc1cc4aef1ae5c7425496d99b30db2f44d7

link/Wasm: improve symbol resolution

This adds additional checks during symbol resolution: - Ensures function signatures match when a symbol will be replaced. - Ensures global types match when the symbol is being replaced. - When both symbols are undefined, ensures they have a matching module name. Those changes ensure the result will pass the validator when the runtime compiles the Wasm module. Additionally, this also slightly changes the behavior when both the existing symbol and new symbol are both defined. Rather than always resulting in a collision, it only results in a collision when both are also weak. Else, the non-weak symbol will be picked.

1 files changed, 96 insertions(+), 4 deletions(-)

src/link/Wasm.zig+96-4
...@@ -463,8 +463,6 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {...@@ -463,8 +463,6 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
463 continue;463 continue;
464 }464 }
465465
466 // TODO: Store undefined symbols so we can verify at the end if they've all been found
467 // if not, emit an error (unless --allow-undefined is enabled).
468 const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name_index);466 const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name_index);
469 if (!maybe_existing.found_existing) {467 if (!maybe_existing.found_existing) {
470 maybe_existing.value_ptr.* = location;468 maybe_existing.value_ptr.* = location;
...@@ -483,8 +481,15 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {...@@ -483,8 +481,15 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
483 break :blk self.objects.items[file].name;481 break :blk self.objects.items[file].name;
484 } else self.name;482 } else self.name;
485483
486 if (!existing_sym.isUndefined()) {484 if (!existing_sym.isUndefined()) outer: {
487 if (!symbol.isUndefined()) {485 if (!symbol.isUndefined()) inner: {
486 if (symbol.isWeak()) {
487 break :inner; // ignore the new symbol (discard it)
488 }
489 if (existing_sym.isWeak()) {
490 break :outer; // existing is weak, while new one isn't. Replace it.
491 }
492 // both are defined and weak, we have a symbol collision.
488 log.err("symbol '{s}' defined multiple times", .{sym_name});493 log.err("symbol '{s}' defined multiple times", .{sym_name});
489 log.err(" first definition in '{s}'", .{existing_file_path});494 log.err(" first definition in '{s}'", .{existing_file_path});
490 log.err(" next definition in '{s}'", .{object.name});495 log.err(" next definition in '{s}'", .{object.name});
...@@ -502,6 +507,53 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {...@@ -502,6 +507,53 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
502 return error.SymbolMismatchingType;507 return error.SymbolMismatchingType;
503 }508 }
504509
510 if (existing_sym.isUndefined() and symbol.isUndefined()) {
511 const existing_name = if (existing_loc.file) |file_index| blk: {
512 const obj = self.objects.items[file_index];
513 const name_index = obj.findImport(symbol.tag.externalType(), existing_sym.index).module_name;
514 break :blk obj.string_table.get(name_index);
515 } else blk: {
516 const name_index = self.imports.get(existing_loc).?.module_name;
517 break :blk self.string_table.get(name_index);
518 };
519
520 const module_index = object.findImport(symbol.tag.externalType(), symbol.index).module_name;
521 const module_name = object.string_table.get(module_index);
522 if (!mem.eql(u8, existing_name, module_name)) {
523 log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{
524 sym_name,
525 existing_name,
526 module_name,
527 });
528 log.err(" first definition in '{s}'", .{existing_file_path});
529 log.err(" next definition in '{s}'", .{object.name});
530 return error.ModuleNameMismatch;
531 }
532 }
533
534 if (existing_sym.tag == .global) {
535 const existing_ty = self.getGlobalType(existing_loc);
536 const new_ty = self.getGlobalType(location);
537 if (existing_ty.mutable != new_ty.mutable or existing_ty.valtype != new_ty.valtype) {
538 log.err("symbol '{s}' mismatching global types", .{sym_name});
539 log.err(" first definition in '{s}'", .{existing_file_path});
540 log.err(" next definition in '{s}'", .{object.name});
541 return error.GlobalTypeMismatch;
542 }
543 }
544
545 if (existing_sym.tag == .function) {
546 const existing_ty = self.getFunctionSignature(existing_loc);
547 const new_ty = self.getFunctionSignature(location);
548 if (!existing_ty.eql(new_ty)) {
549 log.err("symbol '{s}' mismatching function signatures.", .{sym_name});
550 log.err(" expected signature {}, but found signature {}", .{ existing_ty, new_ty });
551 log.err(" first definition in '{s}'", .{existing_file_path});
552 log.err(" next definition in '{s}'", .{object.name});
553 return error.FunctionSignatureMismatch;
554 }
555 }
556
505 // when both symbols are weak, we skip overwriting557 // when both symbols are weak, we skip overwriting
506 if (existing_sym.isWeak() and symbol.isWeak()) {558 if (existing_sym.isWeak() and symbol.isWeak()) {
507 try self.discarded.put(self.base.allocator, location, existing_loc);559 try self.discarded.put(self.base.allocator, location, existing_loc);
...@@ -797,6 +849,46 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void {...@@ -797,6 +849,46 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void {
797 try self.resolved_symbols.put(self.base.allocator, atom.symbolLoc(), {});849 try self.resolved_symbols.put(self.base.allocator, atom.symbolLoc(), {});
798}850}
799851
852/// From a given symbol location, returns its `wasm.GlobalType`.
853/// Asserts the Symbol represents a global.
854fn getGlobalType(self: *const Wasm, loc: SymbolLoc) wasm.GlobalType {
855 const symbol = loc.getSymbol(self);
856 assert(symbol.tag == .global);
857 const is_undefined = symbol.isUndefined();
858 if (loc.file) |file_index| {
859 const obj: Object = self.objects.items[file_index];
860 if (is_undefined) {
861 return obj.findImport(.global, symbol.index).kind.global;
862 }
863 return obj.globals[symbol.index].global_type;
864 }
865 if (is_undefined) {
866 return self.imports.get(loc).?.kind.global;
867 }
868 return self.wasm_globals.items[symbol.index].global_type;
869}
870
871/// From a given symbol location, returns its `wasm.Type`.
872/// Asserts the Symbol represents a function.
873fn getFunctionSignature(self: *const Wasm, loc: SymbolLoc) wasm.Type {
874 const symbol = loc.getSymbol(self);
875 assert(symbol.tag == .function);
876 const is_undefined = symbol.isUndefined();
877 if (loc.file) |file_index| {
878 const obj: Object = self.objects.items[file_index];
879 if (is_undefined) {
880 const ty_index = obj.findImport(.function, symbol.index).kind.function;
881 return obj.func_types[ty_index];
882 }
883 return obj.func_types[obj.functions[symbol.index].type_index];
884 }
885 if (is_undefined) {
886 const ty_index = self.imports.get(loc).?.kind.function;
887 return self.func_types.items[ty_index];
888 }
889 return self.func_types.items[self.functions.get(.{ .file = loc.file, .index = loc.index }).?.type_index];
890}
891
800/// Lowers a constant typed value to a local symbol and atom.892/// Lowers a constant typed value to a local symbol and atom.
801/// Returns the symbol index of the local893/// Returns the symbol index of the local
802/// The given `decl` is the parent decl whom owns the constant.894/// The given `decl` is the parent decl whom owns the constant.