Commit 589aef1537

Luuk de Gram <luuk@degram.dev>
2023-11-13 15:25:17
wasm-linker: mark symbols and its references
Symbols which are exported to the host, or contain the `NO_STRIP` flag, will be marked. All symbols which are referenced by this symbol are marked likewise. We achieve this by parsing all relocations of a symbol, and then marking the symbol it points to within the relocation.
1 parent f3626eb
Changed files (2)
src
src/link/Wasm/Symbol.zig
@@ -79,6 +79,9 @@ pub const Flag = enum(u32) {
     WASM_SYM_NO_STRIP = 0x80,
     /// Indicates a symbol is TLS
     WASM_SYM_TLS = 0x100,
+    /// Zig specific flag. Uses the most significant bit of the flag to annotate whether a symbol is
+    /// alive or not. Dead symbols are allowed to be garbage collected.
+    alive = 0x80000000,
 };
 
 /// Verifies if the given symbol should be imported from the
@@ -92,6 +95,19 @@ pub fn requiresImport(symbol: Symbol) bool {
     return true;
 }
 
+/// Marks a symbol as 'alive', ensuring the garbage collector will not collect the trash.
+pub fn mark(symbol: *Symbol) void {
+    symbol.flags |= @intFromEnum(Flag.alive);
+}
+
+pub fn isAlive(symbol: Symbol) bool {
+    return symbol.flags & @intFromEnum(Flag.alive) != 0;
+}
+
+pub fn isDead(symbol: Symbol) bool {
+    return symbol.flags & @intFromEnum(Flag.alive) == 0;
+}
+
 pub fn isTLS(symbol: Symbol) bool {
     return symbol.flags & @intFromEnum(Flag.WASM_SYM_TLS) != 0;
 }
src/link/Wasm.zig
@@ -3439,12 +3439,13 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
 
     try wasm.setupInitFunctions();
     try wasm.setupStart();
-    try wasm.setupImports();
 
     for (wasm.objects.items, 0..) |*object, object_index| {
         try object.parseIntoAtoms(gpa, @as(u16, @intCast(object_index)), wasm);
     }
 
+    wasm.markReferences();
+    try wasm.setupImports();
     try wasm.allocateAtoms();
     try wasm.setupMemory();
     wasm.allocateVirtualAddresses();
@@ -3529,6 +3530,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
     try wasm.setupInitFunctions();
     try wasm.setupErrorsLen();
     try wasm.setupStart();
+    wasm.markReferences();
     try wasm.setupImports();
     if (wasm.base.options.module) |mod| {
         var decl_it = wasm.decls.iterator();
@@ -5026,3 +5028,38 @@ pub fn storeDeclType(wasm: *Wasm, decl_index: InternPool.DeclIndex, func_type: s
     try wasm.atom_types.put(wasm.base.allocator, atom_index, index);
     return index;
 }
+
+/// Verifies all resolved symbols and checks whether itself needs to be marked alive,
+/// as well as any of its references.
+fn markReferences(wasm: *Wasm) void {
+    const tracy = trace(@src());
+    defer tracy.end();
+    for (wasm.resolved_symbols.keys()) |sym_loc| {
+        const sym = sym_loc.getSymbol(wasm);
+        if (sym.isExported(wasm.base.options.rdynamic) or sym.isNoStrip()) {
+            wasm.mark(sym_loc);
+        }
+    }
+}
+
+/// Marks a symbol as 'alive' recursively so itself and any references it contains to
+/// other symbols will not be omit from the binary.
+fn mark(wasm: *Wasm, loc: SymbolLoc) void {
+    const symbol = loc.getSymbol(wasm);
+    if (symbol.isAlive()) {
+        // Symbol is already marked alive, including its references.
+        // This means we can skip it so we don't end up marking the same symbols
+        // multiple times.
+        return;
+    }
+    symbol.mark();
+
+    if (wasm.symbol_atom.get(loc)) |atom_index| {
+        const atom = wasm.getAtom(atom_index);
+        const relocations: []const types.Relocation = atom.relocs.items;
+        for (relocations) |reloc| {
+            const target_loc: SymbolLoc = .{ .index = reloc.index, .file = loc.file };
+            wasm.mark(target_loc.finalLoc(wasm));
+        }
+    }
+}