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 {...@@ -2938,49 +2938,31 @@ fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
2938 return WValue{ .stack = {} };2938 return WValue{ .stack = {} };
2939}2939}
29402940
2941fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {2941fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue {
2942 const mod = func.bin_file.base.options.module.?;2942 const mod = func.bin_file.base.options.module.?;
2943 const ptr = mod.intern_pool.indexToKey(ptr_val.ip_index).ptr;2943 const ptr = mod.intern_pool.indexToKey(ptr_val.ip_index).ptr;
2944 switch (ptr.addr) {2944 switch (ptr.addr) {
2945 .decl => |decl_index| {2945 .decl => |decl_index| {
2946 return func.lowerParentPtrDecl(ptr_val, decl_index, 0);2946 return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
2947 },2947 },
2948 .mut_decl => |mut_decl| {2948 .mut_decl => |mut_decl| {
2949 const decl_index = mut_decl.decl;2949 const decl_index = mut_decl.decl;
2950 return func.lowerParentPtrDecl(ptr_val, decl_index, 0);2950 return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
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());
2955 },2951 },
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),
2956 .comptime_field => unreachable,2955 .comptime_field => unreachable,
2957 .elem => |elem| {2956 .elem => |elem| {
2958 const index = elem.index;2957 const index = elem.index;
2959 const elem_type = mod.intern_pool.typeOf(elem.base).toType().elemType2(mod);2958 const elem_type = mod.intern_pool.typeOf(elem.base).toType().elemType2(mod);
2960 const offset = index * elem_type.abiSize(mod);2959 const elem_offset = index * elem_type.abiSize(mod);
2961 const array_ptr = try func.lowerParentPtr(elem.base.toValue());2960 return func.lowerParentPtr(elem.base.toValue(), @intCast(u32, elem_offset + offset));
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 };
2978 },2961 },
2979 .field => |field| {2962 .field => |field| {
2980 const parent_ty = mod.intern_pool.typeOf(field.base).toType().childType(mod);2963 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)) {
2984 .Struct => switch (parent_ty.containerLayout(mod)) {2966 .Struct => switch (parent_ty.containerLayout(mod)) {
2985 .Packed => parent_ty.packedStructFieldByteOffset(@intCast(usize, field.index), mod),2967 .Packed => parent_ty.packedStructFieldByteOffset(@intCast(usize, field.index), mod),
2986 else => parent_ty.structFieldOffset(@intCast(usize, field.index), mod),2968 else => parent_ty.structFieldOffset(@intCast(usize, field.index), mod),
...@@ -2993,8 +2975,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {...@@ -2993,8 +2975,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
2993 if (layout.payload_align > layout.tag_align) break :blk 0;2975 if (layout.payload_align > layout.tag_align) break :blk 0;
29942976
2995 // tag is stored first so calculate offset from where payload starts2977 // 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));2978 break :blk @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
2997 break :blk offset;
2998 },2979 },
2999 },2980 },
3000 .Pointer => switch (parent_ty.ptrSize(mod)) {2981 .Pointer => switch (parent_ty.ptrSize(mod)) {
...@@ -3007,22 +2988,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {...@@ -3007,22 +2988,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
3007 },2988 },
3008 else => unreachable,2989 else => unreachable,
3009 };2990 };
30102991 return func.lowerParentPtr(field.base.toValue(), @intCast(u32, offset + field_offset));
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 };
3026 },2992 },
3027 }2993 }
3028}2994}
...@@ -3042,6 +3008,17 @@ fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Ind...@@ -3042,6 +3008,17 @@ fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Ind
3042 }3008 }
30433009
3044 const decl = mod.declPtr(decl_index);3010 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 }
3045 if (decl.ty.zigTypeTag(mod) != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime(mod)) {3022 if (decl.ty.zigTypeTag(mod) != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime(mod)) {
3046 return WValue{ .imm32 = 0xaaaaaaaa };3023 return WValue{ .imm32 = 0xaaaaaaaa };
3047 }3024 }
...@@ -3230,7 +3207,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {...@@ -3230,7 +3207,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
3230 .decl => |decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl, 0),3207 .decl => |decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl, 0),
3231 .mut_decl => |mut_decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, mut_decl.decl, 0),3208 .mut_decl => |mut_decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, mut_decl.decl, 0),
3232 .int => |int| return func.lowerConstant(int.toValue(), mod.intern_pool.typeOf(int).toType()),3209 .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),
3234 else => return func.fail("Wasm TODO: lowerConstant for other const addr tag {}", .{ptr.addr}),3211 else => return func.fail("Wasm TODO: lowerConstant for other const addr tag {}", .{ptr.addr}),
3235 },3212 },
3236 .opt => if (ty.optionalReprIsPayload(mod)) {3213 .opt => if (ty.optionalReprIsPayload(mod)) {
src/codegen.zig+4
...@@ -598,6 +598,10 @@ pub fn generateSymbol(...@@ -598,6 +598,10 @@ pub fn generateSymbol(
598 .fail => |em| return Result{ .fail = em },598 .fail => |em| return Result{ .fail = em },
599 }599 }
600 }600 }
601
602 if (layout.padding > 0) {
603 try code.writer().writeByteNTimes(0, layout.padding);
604 }
601 },605 },
602 .memoized_call => unreachable,606 .memoized_call => unreachable,
603 }607 }
src/link/Wasm.zig+57-37
...@@ -1703,6 +1703,7 @@ pub fn updateDeclExports(...@@ -1703,6 +1703,7 @@ pub fn updateDeclExports(
1703 const decl = mod.declPtr(decl_index);1703 const decl = mod.declPtr(decl_index);
1704 const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);1704 const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
1705 const atom = wasm.getAtom(atom_index);1705 const atom = wasm.getAtom(atom_index);
1706 const atom_sym = atom.symbolLoc().getSymbol(wasm).*;
1706 const gpa = mod.gpa;1707 const gpa = mod.gpa;
17071708
1708 for (exports) |exp| {1709 for (exports) |exp| {
...@@ -1716,43 +1717,21 @@ pub fn updateDeclExports(...@@ -1716,43 +1717,21 @@ pub fn updateDeclExports(
1716 continue;1717 continue;
1717 }1718 }
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
1749 const exported_atom_index = try wasm.getOrCreateAtomForDecl(exp.exported_decl);1720 const exported_atom_index = try wasm.getOrCreateAtomForDecl(exp.exported_decl);
1750 const exported_atom = wasm.getAtom(exported_atom_index);1721 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));
1751 const sym_loc = exported_atom.symbolLoc();1723 const sym_loc = exported_atom.symbolLoc();
1752 const symbol = sym_loc.getSymbol(wasm);1724 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
1753 switch (exp.opts.linkage) {1731 switch (exp.opts.linkage) {
1754 .Internal => {1732 .Internal => {
1755 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);1733 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
1734 symbol.setFlag(.WASM_SYM_BINDING_WEAK);
1756 },1735 },
1757 .Weak => {1736 .Weak => {
1758 symbol.setFlag(.WASM_SYM_BINDING_WEAK);1737 symbol.setFlag(.WASM_SYM_BINDING_WEAK);
...@@ -1768,22 +1747,52 @@ pub fn updateDeclExports(...@@ -1768,22 +1747,52 @@ pub fn updateDeclExports(
1768 continue;1747 continue;
1769 },1748 },
1770 }1749 }
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
1771 // Ensure the symbol will be exported using the given name1786 // Ensure the symbol will be exported using the given name
1772 if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) {1787 if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) {
1773 try wasm.export_names.put(wasm.base.allocator, sym_loc, export_name);1788 try wasm.export_names.put(wasm.base.allocator, sym_loc, export_name);
1774 }1789 }
17751790
1776 symbol.setGlobal(true);
1777 symbol.setUndefined(false);
1778 try wasm.globals.put(1791 try wasm.globals.put(
1779 wasm.base.allocator,1792 wasm.base.allocator,
1780 export_name,1793 export_name,
1781 sym_loc,1794 sym_loc,
1782 );1795 );
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);
1787 }1796 }
1788}1797}
17891798
...@@ -1900,6 +1909,17 @@ pub fn addOrUpdateImport(...@@ -1900,6 +1909,17 @@ pub fn addOrUpdateImport(
1900 global_gop.value_ptr.* = loc;1909 global_gop.value_ptr.* = loc;
1901 try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});1910 try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});
1902 try wasm.undefs.putNoClobber(wasm.base.allocator, decl_name_index, loc);1911 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 );
1903 }1923 }
19041924
1905 if (type_index) |ty_index| {1925 if (type_index) |ty_index| {
...@@ -1915,8 +1935,8 @@ pub fn addOrUpdateImport(...@@ -1915,8 +1935,8 @@ pub fn addOrUpdateImport(
1915 };1935 };
1916 }1936 }
1917 } else {1937 } else {
1938 // non-functions will not be imported from the runtime, but only resolved during link-time
1918 symbol.tag = .data;1939 symbol.tag = .data;
1919 return; // non-functions will not be imported from the runtime, but only resolved during link-time
1920 }1940 }
1921}1941}
19221942
test/behavior.zig+1-5
...@@ -158,6 +158,7 @@ test {...@@ -158,6 +158,7 @@ test {
158 _ = @import("behavior/enum.zig");158 _ = @import("behavior/enum.zig");
159 _ = @import("behavior/error.zig");159 _ = @import("behavior/error.zig");
160 _ = @import("behavior/eval.zig");160 _ = @import("behavior/eval.zig");
161 _ = @import("behavior/export_self_referential_type_info.zig");
161 _ = @import("behavior/field_parent_ptr.zig");162 _ = @import("behavior/field_parent_ptr.zig");
162 _ = @import("behavior/floatop.zig");163 _ = @import("behavior/floatop.zig");
163 _ = @import("behavior/fn.zig");164 _ = @import("behavior/fn.zig");
...@@ -241,7 +242,6 @@ test {...@@ -241,7 +242,6 @@ test {
241 if (builtin.zig_backend != .stage2_arm and242 if (builtin.zig_backend != .stage2_arm and
242 builtin.zig_backend != .stage2_x86_64 and243 builtin.zig_backend != .stage2_x86_64 and
243 builtin.zig_backend != .stage2_aarch64 and244 builtin.zig_backend != .stage2_aarch64 and
244 builtin.zig_backend != .stage2_wasm and
245 builtin.zig_backend != .stage2_c and245 builtin.zig_backend != .stage2_c and
246 builtin.zig_backend != .stage2_spirv64)246 builtin.zig_backend != .stage2_spirv64)
247 {247 {
...@@ -250,8 +250,4 @@ test {...@@ -250,8 +250,4 @@ test {
250 _ = @import("behavior/bugs/14198.zig");250 _ = @import("behavior/bugs/14198.zig");
251 _ = @import("behavior/export.zig");251 _ = @import("behavior/export.zig");
252 }252 }
253
254 if (builtin.zig_backend != .stage2_wasm) {
255 _ = @import("behavior/export_self_referential_type_info.zig");
256 }
257}253}
test/behavior/bugs/1381.zig-1
...@@ -17,7 +17,6 @@ test "union that needs padding bytes inside an array" {...@@ -17,7 +17,6 @@ test "union that needs padding bytes inside an array" {
17 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;17 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
18 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO18 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
19 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;19 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
20 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
2120
22 var as = [_]A{21 var as = [_]A{
23 A{ .B = B{ .D = 1 } },22 A{ .B = B{ .D = 1 } },
test/behavior/bugs/529.zig-1
...@@ -11,7 +11,6 @@ comptime {...@@ -11,7 +11,6 @@ comptime {
11const builtin = @import("builtin");11const builtin = @import("builtin");
1212
13test "issue 529 fixed" {13test "issue 529 fixed" {
14 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
15 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO14 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
16 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO15 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
17 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO16 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO