Commit 4f2143becc

Luuk de Gram <luuk@degram.dev>
2022-08-16 20:50:50
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 parent 63c25cc
Changed files (1)
src
src/link/Wasm.zig
@@ -463,8 +463,6 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
             continue;
         }
 
-        // TODO: Store undefined symbols so we can verify at the end if they've all been found
-        // if not, emit an error (unless --allow-undefined is enabled).
         const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name_index);
         if (!maybe_existing.found_existing) {
             maybe_existing.value_ptr.* = location;
@@ -483,8 +481,15 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
             break :blk self.objects.items[file].name;
         } else self.name;
 
-        if (!existing_sym.isUndefined()) {
-            if (!symbol.isUndefined()) {
+        if (!existing_sym.isUndefined()) outer: {
+            if (!symbol.isUndefined()) inner: {
+                if (symbol.isWeak()) {
+                    break :inner; // ignore the new symbol (discard it)
+                }
+                if (existing_sym.isWeak()) {
+                    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}'", .{object.name});
@@ -502,6 +507,53 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
             return error.SymbolMismatchingType;
         }
 
+        if (existing_sym.isUndefined() and symbol.isUndefined()) {
+            const existing_name = if (existing_loc.file) |file_index| blk: {
+                const obj = self.objects.items[file_index];
+                const name_index = obj.findImport(symbol.tag.externalType(), existing_sym.index).module_name;
+                break :blk obj.string_table.get(name_index);
+            } else blk: {
+                const name_index = self.imports.get(existing_loc).?.module_name;
+                break :blk self.string_table.get(name_index);
+            };
+
+            const module_index = object.findImport(symbol.tag.externalType(), symbol.index).module_name;
+            const module_name = object.string_table.get(module_index);
+            if (!mem.eql(u8, existing_name, module_name)) {
+                log.err("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}'", .{object.name});
+                return error.ModuleNameMismatch;
+            }
+        }
+
+        if (existing_sym.tag == .global) {
+            const existing_ty = self.getGlobalType(existing_loc);
+            const new_ty = self.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}'", .{object.name});
+                return error.GlobalTypeMismatch;
+            }
+        }
+
+        if (existing_sym.tag == .function) {
+            const existing_ty = self.getFunctionSignature(existing_loc);
+            const new_ty = self.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}'", .{object.name});
+                return error.FunctionSignatureMismatch;
+            }
+        }
+
         // when both symbols are weak, we skip overwriting
         if (existing_sym.isWeak() and symbol.isWeak()) {
             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 {
     try self.resolved_symbols.put(self.base.allocator, atom.symbolLoc(), {});
 }
 
+/// From a given symbol location, returns its `wasm.GlobalType`.
+/// Asserts the Symbol represents a global.
+fn getGlobalType(self: *const Wasm, loc: SymbolLoc) wasm.GlobalType {
+    const symbol = loc.getSymbol(self);
+    assert(symbol.tag == .global);
+    const is_undefined = symbol.isUndefined();
+    if (loc.file) |file_index| {
+        const obj: Object = self.objects.items[file_index];
+        if (is_undefined) {
+            return obj.findImport(.global, symbol.index).kind.global;
+        }
+        return obj.globals[symbol.index].global_type;
+    }
+    if (is_undefined) {
+        return self.imports.get(loc).?.kind.global;
+    }
+    return self.wasm_globals.items[symbol.index].global_type;
+}
+
+/// From a given symbol location, returns its `wasm.Type`.
+/// Asserts the Symbol represents a function.
+fn getFunctionSignature(self: *const Wasm, loc: SymbolLoc) wasm.Type {
+    const symbol = loc.getSymbol(self);
+    assert(symbol.tag == .function);
+    const is_undefined = symbol.isUndefined();
+    if (loc.file) |file_index| {
+        const obj: Object = self.objects.items[file_index];
+        if (is_undefined) {
+            const ty_index = obj.findImport(.function, symbol.index).kind.function;
+            return obj.func_types[ty_index];
+        }
+        return obj.func_types[obj.functions[symbol.index].type_index];
+    }
+    if (is_undefined) {
+        const ty_index = self.imports.get(loc).?.kind.function;
+        return self.func_types.items[ty_index];
+    }
+    return self.func_types.items[self.functions.get(.{ .file = loc.file, .index = loc.index }).?.type_index];
+}
+
 /// Lowers a constant typed value to a local symbol and atom.
 /// Returns the symbol index of the local
 /// The given `decl` is the parent decl whom owns the constant.