authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-06-13 21:08:02+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-06-16 17:16:55+02:00
log729f822e311f3bce1e7bd99bcf71937145451a4c
tree48e61ad54dd9203cfcf73e4b63266066d56f4d4a
parent5d9e8f27d0dc131e0b4154c5f65376f2fb9f3500
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: correctly resolve exported symbols

When compiling Zig code using the Wasm backend, we would previously incorrectly resolve exported symbols as it would not correctly remove existing symbols if they were to be overwritten. This meant that undefined symbols could cause collisions although they should be resolved by the exported symbol.

1 files changed, 45 insertions(+), 36 deletions(-)

src/link/Wasm.zig+45-36
......@@ -1703,6 +1703,7 @@ pub fn updateDeclExports(
17031703 const decl = mod.declPtr(decl_index);
17041704 const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
17051705 const atom = wasm.getAtom(atom_index);
1706 const atom_sym = atom.symbolLoc().getSymbol(wasm).*;
17061707 const gpa = mod.gpa;
17071708
17081709 for (exports) |exp| {
......@@ -1716,43 +1717,21 @@ pub fn updateDeclExports(
17161717 continue;
17171718 }
17181719
1719 const export_name = try wasm.string_table.put(wasm.base.allocator, mod.intern_pool.stringToSlice(exp.opts.name));
1720 if (wasm.globals.getPtr(export_name)) |existing_loc| {
1721 if (existing_loc.index == atom.sym_index) continue;
1722 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
1723
1724 const exp_is_weak = exp.opts.linkage == .Internal or exp.opts.linkage == .Weak;
1725 // When both the to-be-exported symbol and the already existing symbol
1726 // are strong symbols, we have a linker error.
1727 // In the other case we replace one with the other.
1728 if (!exp_is_weak and !existing_sym.isWeak()) {
1729 try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
1730 gpa,
1731 decl.srcLoc(mod),
1732 \\LinkError: symbol '{}' defined multiple times
1733 \\ first definition in '{s}'
1734 \\ next definition in '{s}'
1735 ,
1736 .{ exp.opts.name.fmt(&mod.intern_pool), wasm.name, wasm.name },
1737 ));
1738 continue;
1739 } else if (exp_is_weak) {
1740 continue; // to-be-exported symbol is weak, so we keep the existing symbol
1741 } else {
1742 // TODO: Revisit this, why was this needed?
1743 existing_loc.index = atom.sym_index;
1744 existing_loc.file = null;
1745 // exp.link.wasm.sym_index = existing_loc.index;
1746 }
1747 }
1748
17491720 const exported_atom_index = try wasm.getOrCreateAtomForDecl(exp.exported_decl);
17501721 const exported_atom = wasm.getAtom(exported_atom_index);
1722 const export_name = try wasm.string_table.put(wasm.base.allocator, mod.intern_pool.stringToSlice(exp.opts.name));
17511723 const sym_loc = exported_atom.symbolLoc();
17521724 const symbol = sym_loc.getSymbol(wasm);
1725 symbol.setGlobal(true);
1726 symbol.setUndefined(false);
1727 symbol.index = atom_sym.index;
1728 symbol.tag = atom_sym.tag;
1729 symbol.name = atom_sym.name;
1730
17531731 switch (exp.opts.linkage) {
17541732 .Internal => {
17551733 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
1734 symbol.setFlag(.WASM_SYM_BINDING_WEAK);
17561735 },
17571736 .Weak => {
17581737 symbol.setFlag(.WASM_SYM_BINDING_WEAK);
......@@ -1768,22 +1747,52 @@ pub fn updateDeclExports(
17681747 continue;
17691748 },
17701749 }
1750
1751 if (wasm.globals.get(export_name)) |existing_loc| {
1752 if (existing_loc.index == atom.sym_index) continue;
1753 const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
1754
1755 if (!existing_sym.isUndefined()) blk: {
1756 if (symbol.isWeak()) {
1757 try wasm.discarded.put(wasm.base.allocator, existing_loc, sym_loc);
1758 continue; // to-be-exported symbol is weak, so we keep the existing symbol
1759 }
1760
1761 // new symbol is not weak while existing is, replace existing symbol
1762 if (existing_sym.isWeak()) {
1763 break :blk;
1764 }
1765 // When both the to-be-exported symbol and the already existing symbol
1766 // are strong symbols, we have a linker error.
1767 // In the other case we replace one with the other.
1768 try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
1769 gpa,
1770 decl.srcLoc(mod),
1771 \\LinkError: symbol '{}' defined multiple times
1772 \\ first definition in '{s}'
1773 \\ next definition in '{s}'
1774 ,
1775 .{ exp.opts.name.fmt(&mod.intern_pool), wasm.name, wasm.name },
1776 ));
1777 continue;
1778 }
1779
1780 // in this case the existing symbol must be replaced either because it's weak or undefined.
1781 try wasm.discarded.put(wasm.base.allocator, existing_loc, sym_loc);
1782 _ = wasm.imports.remove(existing_loc);
1783 _ = wasm.undefs.swapRemove(existing_sym.name);
1784 }
1785
17711786 // Ensure the symbol will be exported using the given name
17721787 if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) {
17731788 try wasm.export_names.put(wasm.base.allocator, sym_loc, export_name);
17741789 }
17751790
1776 symbol.setGlobal(true);
1777 symbol.setUndefined(false);
17781791 try wasm.globals.put(
17791792 wasm.base.allocator,
17801793 export_name,
17811794 sym_loc,
17821795 );
1783
1784 // if the symbol was previously undefined, remove it as an import
1785 _ = wasm.imports.remove(sym_loc);
1786 _ = wasm.undefs.swapRemove(export_name);
17871796 }
17881797}
17891798