authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-16 22:03:35-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-16 22:03:35-07:00
loga10ddba9218abf452ccc2a244222b4d69b606865
treea2d1316cf118d344e012432b015ac9d76d217d2f
parentabfa2158820c963cddc0c861168b16021d846825
parent44b322ce6410a0fab7c3cbdfc35bb1530a31a304
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16064 from Luukdegram/wasm-linker

wasm/linker: symbol resolution improvements

6 files changed, 85 insertions(+), 90 deletions(-)

src/arch/wasm/CodeGen.zig+23-46
......@@ -2938,49 +2938,31 @@ fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
29382938 return WValue{ .stack = {} };
29392939}
29402940
2941fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
2941fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue {
29422942 const mod = func.bin_file.base.options.module.?;
29432943 const ptr = mod.intern_pool.indexToKey(ptr_val.ip_index).ptr;
29442944 switch (ptr.addr) {
29452945 .decl => |decl_index| {
2946 return func.lowerParentPtrDecl(ptr_val, decl_index, 0);
2946 return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
29472947 },
29482948 .mut_decl => |mut_decl| {
29492949 const decl_index = mut_decl.decl;
2950 return func.lowerParentPtrDecl(ptr_val, decl_index, 0);
2951 },
2952 .int, .eu_payload => |tag| return func.fail("TODO: Implement lowerParentPtr for {}", .{tag}),
2953 .opt_payload => |base_ptr| {
2954 return func.lowerParentPtr(base_ptr.toValue());
2950 return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
29552951 },
2952 .eu_payload => |tag| return func.fail("TODO: Implement lowerParentPtr for {}", .{tag}),
2953 .int => |base| return func.lowerConstant(base.toValue(), Type.usize),
2954 .opt_payload => |base_ptr| return func.lowerParentPtr(base_ptr.toValue(), offset),
29562955 .comptime_field => unreachable,
29572956 .elem => |elem| {
29582957 const index = elem.index;
29592958 const elem_type = mod.intern_pool.typeOf(elem.base).toType().elemType2(mod);
2960 const offset = index * elem_type.abiSize(mod);
2961 const array_ptr = try func.lowerParentPtr(elem.base.toValue());
2962
2963 return switch (array_ptr) {
2964 .memory => |ptr_| WValue{
2965 .memory_offset = .{
2966 .pointer = ptr_,
2967 .offset = @intCast(u32, offset),
2968 },
2969 },
2970 .memory_offset => |mem_off| WValue{
2971 .memory_offset = .{
2972 .pointer = mem_off.pointer,
2973 .offset = @intCast(u32, offset) + mem_off.offset,
2974 },
2975 },
2976 else => unreachable,
2977 };
2959 const elem_offset = index * elem_type.abiSize(mod);
2960 return func.lowerParentPtr(elem.base.toValue(), @intCast(u32, elem_offset + offset));
29782961 },
29792962 .field => |field| {
29802963 const parent_ty = mod.intern_pool.typeOf(field.base).toType().childType(mod);
2981 const parent_ptr = try func.lowerParentPtr(field.base.toValue());
29822964
2983 const offset = switch (parent_ty.zigTypeTag(mod)) {
2965 const field_offset = switch (parent_ty.zigTypeTag(mod)) {
29842966 .Struct => switch (parent_ty.containerLayout(mod)) {
29852967 .Packed => parent_ty.packedStructFieldByteOffset(@intCast(usize, field.index), mod),
29862968 else => parent_ty.structFieldOffset(@intCast(usize, field.index), mod),
......@@ -2993,8 +2975,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
29932975 if (layout.payload_align > layout.tag_align) break :blk 0;
29942976
29952977 // tag is stored first so calculate offset from where payload starts
2996 const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
2997 break :blk offset;
2978 break :blk @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
29982979 },
29992980 },
30002981 .Pointer => switch (parent_ty.ptrSize(mod)) {
......@@ -3007,22 +2988,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
30072988 },
30082989 else => unreachable,
30092990 };
3010
3011 return switch (parent_ptr) {
3012 .memory => |ptr_| WValue{
3013 .memory_offset = .{
3014 .pointer = ptr_,
3015 .offset = @intCast(u32, offset),
3016 },
3017 },
3018 .memory_offset => |mem_off| WValue{
3019 .memory_offset = .{
3020 .pointer = mem_off.pointer,
3021 .offset = @intCast(u32, offset) + mem_off.offset,
3022 },
3023 },
3024 else => unreachable,
3025 };
2991 return func.lowerParentPtr(field.base.toValue(), @intCast(u32, offset + field_offset));
30262992 },
30272993 }
30282994}
......@@ -3042,6 +3008,17 @@ fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Ind
30423008 }
30433009
30443010 const decl = mod.declPtr(decl_index);
3011 // check if decl is an alias to a function, in which case we
3012 // want to lower the actual decl, rather than the alias itself.
3013 if (decl.val.getFunction(mod)) |func_val| {
3014 if (func_val.owner_decl != decl_index) {
3015 return func.lowerDeclRefValue(tv, func_val.owner_decl, offset);
3016 }
3017 } else if (decl.val.getExternFunc(mod)) |func_val| {
3018 if (func_val.decl != decl_index) {
3019 return func.lowerDeclRefValue(tv, func_val.decl, offset);
3020 }
3021 }
30453022 if (decl.ty.zigTypeTag(mod) != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime(mod)) {
30463023 return WValue{ .imm32 = 0xaaaaaaaa };
30473024 }
......@@ -3230,7 +3207,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
32303207 .decl => |decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl, 0),
32313208 .mut_decl => |mut_decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, mut_decl.decl, 0),
32323209 .int => |int| return func.lowerConstant(int.toValue(), mod.intern_pool.typeOf(int).toType()),
3233 .opt_payload, .elem, .field => return func.lowerParentPtr(val),
3210 .opt_payload, .elem, .field => return func.lowerParentPtr(val, 0),
32343211 else => return func.fail("Wasm TODO: lowerConstant for other const addr tag {}", .{ptr.addr}),
32353212 },
32363213 .opt => if (ty.optionalReprIsPayload(mod)) {
src/codegen.zig+4
......@@ -598,6 +598,10 @@ pub fn generateSymbol(
598598 .fail => |em| return Result{ .fail = em },
599599 }
600600 }
601
602 if (layout.padding > 0) {
603 try code.writer().writeByteNTimes(0, layout.padding);
604 }
601605 },
602606 .memoized_call => unreachable,
603607 }
src/link/Wasm.zig+57-37
......@@ -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
......@@ -1900,6 +1909,17 @@ pub fn addOrUpdateImport(
19001909 global_gop.value_ptr.* = loc;
19011910 try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});
19021911 try wasm.undefs.putNoClobber(wasm.base.allocator, decl_name_index, loc);
1912 } else if (global_gop.value_ptr.*.index != symbol_index) {
1913 // We are not updating a symbol, but found an existing global
1914 // symbol with the same name. This means we always favor the
1915 // existing symbol, regardless whether it's defined or not.
1916 // We can also skip storing the import as we will not output
1917 // this symbol.
1918 return wasm.discarded.put(
1919 wasm.base.allocator,
1920 .{ .file = null, .index = symbol_index },
1921 global_gop.value_ptr.*,
1922 );
19031923 }
19041924
19051925 if (type_index) |ty_index| {
......@@ -1915,8 +1935,8 @@ pub fn addOrUpdateImport(
19151935 };
19161936 }
19171937 } else {
1938 // non-functions will not be imported from the runtime, but only resolved during link-time
19181939 symbol.tag = .data;
1919 return; // non-functions will not be imported from the runtime, but only resolved during link-time
19201940 }
19211941}
19221942
test/behavior.zig+1-5
......@@ -158,6 +158,7 @@ test {
158158 _ = @import("behavior/enum.zig");
159159 _ = @import("behavior/error.zig");
160160 _ = @import("behavior/eval.zig");
161 _ = @import("behavior/export_self_referential_type_info.zig");
161162 _ = @import("behavior/field_parent_ptr.zig");
162163 _ = @import("behavior/floatop.zig");
163164 _ = @import("behavior/fn.zig");
......@@ -241,7 +242,6 @@ test {
241242 if (builtin.zig_backend != .stage2_arm and
242243 builtin.zig_backend != .stage2_x86_64 and
243244 builtin.zig_backend != .stage2_aarch64 and
244 builtin.zig_backend != .stage2_wasm and
245245 builtin.zig_backend != .stage2_c and
246246 builtin.zig_backend != .stage2_spirv64)
247247 {
......@@ -250,8 +250,4 @@ test {
250250 _ = @import("behavior/bugs/14198.zig");
251251 _ = @import("behavior/export.zig");
252252 }
253
254 if (builtin.zig_backend != .stage2_wasm) {
255 _ = @import("behavior/export_self_referential_type_info.zig");
256 }
257253}
test/behavior/bugs/1381.zig-1
......@@ -17,7 +17,6 @@ test "union that needs padding bytes inside an array" {
1717 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
1818 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1919 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
20 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
2120
2221 var as = [_]A{
2322 A{ .B = B{ .D = 1 } },
test/behavior/bugs/529.zig-1
......@@ -11,7 +11,6 @@ comptime {
1111const builtin = @import("builtin");
1212
1313test "issue 529 fixed" {
14 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1514 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
1615 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1716 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO