Merge pull request #16064 from Luukdegram/wasm-linker

wasm/linker: symbol resolution improvements
This commit is contained in:
Andrew Kelley 2023-06-16 22:03:35 -07:00 committed by GitHub
commit a10ddba921
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 90 deletions

View File

@ -2938,49 +2938,31 @@ fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
return WValue{ .stack = {} };
}
fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue {
const mod = func.bin_file.base.options.module.?;
const ptr = mod.intern_pool.indexToKey(ptr_val.ip_index).ptr;
switch (ptr.addr) {
.decl => |decl_index| {
return func.lowerParentPtrDecl(ptr_val, decl_index, 0);
return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
},
.mut_decl => |mut_decl| {
const decl_index = mut_decl.decl;
return func.lowerParentPtrDecl(ptr_val, decl_index, 0);
},
.int, .eu_payload => |tag| return func.fail("TODO: Implement lowerParentPtr for {}", .{tag}),
.opt_payload => |base_ptr| {
return func.lowerParentPtr(base_ptr.toValue());
return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
},
.eu_payload => |tag| return func.fail("TODO: Implement lowerParentPtr for {}", .{tag}),
.int => |base| return func.lowerConstant(base.toValue(), Type.usize),
.opt_payload => |base_ptr| return func.lowerParentPtr(base_ptr.toValue(), offset),
.comptime_field => unreachable,
.elem => |elem| {
const index = elem.index;
const elem_type = mod.intern_pool.typeOf(elem.base).toType().elemType2(mod);
const offset = index * elem_type.abiSize(mod);
const array_ptr = try func.lowerParentPtr(elem.base.toValue());
return switch (array_ptr) {
.memory => |ptr_| WValue{
.memory_offset = .{
.pointer = ptr_,
.offset = @intCast(u32, offset),
},
},
.memory_offset => |mem_off| WValue{
.memory_offset = .{
.pointer = mem_off.pointer,
.offset = @intCast(u32, offset) + mem_off.offset,
},
},
else => unreachable,
};
const elem_offset = index * elem_type.abiSize(mod);
return func.lowerParentPtr(elem.base.toValue(), @intCast(u32, elem_offset + offset));
},
.field => |field| {
const parent_ty = mod.intern_pool.typeOf(field.base).toType().childType(mod);
const parent_ptr = try func.lowerParentPtr(field.base.toValue());
const offset = switch (parent_ty.zigTypeTag(mod)) {
const field_offset = switch (parent_ty.zigTypeTag(mod)) {
.Struct => switch (parent_ty.containerLayout(mod)) {
.Packed => parent_ty.packedStructFieldByteOffset(@intCast(usize, field.index), mod),
else => parent_ty.structFieldOffset(@intCast(usize, field.index), mod),
@ -2993,8 +2975,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
if (layout.payload_align > layout.tag_align) break :blk 0;
// tag is stored first so calculate offset from where payload starts
const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
break :blk offset;
break :blk @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
},
},
.Pointer => switch (parent_ty.ptrSize(mod)) {
@ -3007,22 +2988,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value) InnerError!WValue {
},
else => unreachable,
};
return switch (parent_ptr) {
.memory => |ptr_| WValue{
.memory_offset = .{
.pointer = ptr_,
.offset = @intCast(u32, offset),
},
},
.memory_offset => |mem_off| WValue{
.memory_offset = .{
.pointer = mem_off.pointer,
.offset = @intCast(u32, offset) + mem_off.offset,
},
},
else => unreachable,
};
return func.lowerParentPtr(field.base.toValue(), @intCast(u32, offset + field_offset));
},
}
}
@ -3042,6 +3008,17 @@ fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Ind
}
const decl = mod.declPtr(decl_index);
// check if decl is an alias to a function, in which case we
// want to lower the actual decl, rather than the alias itself.
if (decl.val.getFunction(mod)) |func_val| {
if (func_val.owner_decl != decl_index) {
return func.lowerDeclRefValue(tv, func_val.owner_decl, offset);
}
} else if (decl.val.getExternFunc(mod)) |func_val| {
if (func_val.decl != decl_index) {
return func.lowerDeclRefValue(tv, func_val.decl, offset);
}
}
if (decl.ty.zigTypeTag(mod) != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime(mod)) {
return WValue{ .imm32 = 0xaaaaaaaa };
}
@ -3230,7 +3207,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
.decl => |decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl, 0),
.mut_decl => |mut_decl| return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, mut_decl.decl, 0),
.int => |int| return func.lowerConstant(int.toValue(), mod.intern_pool.typeOf(int).toType()),
.opt_payload, .elem, .field => return func.lowerParentPtr(val),
.opt_payload, .elem, .field => return func.lowerParentPtr(val, 0),
else => return func.fail("Wasm TODO: lowerConstant for other const addr tag {}", .{ptr.addr}),
},
.opt => if (ty.optionalReprIsPayload(mod)) {

View File

@ -598,6 +598,10 @@ pub fn generateSymbol(
.fail => |em| return Result{ .fail = em },
}
}
if (layout.padding > 0) {
try code.writer().writeByteNTimes(0, layout.padding);
}
},
.memoized_call => unreachable,
}

View File

@ -1703,6 +1703,7 @@ pub fn updateDeclExports(
const decl = mod.declPtr(decl_index);
const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
const atom = wasm.getAtom(atom_index);
const atom_sym = atom.symbolLoc().getSymbol(wasm).*;
const gpa = mod.gpa;
for (exports) |exp| {
@ -1716,43 +1717,21 @@ pub fn updateDeclExports(
continue;
}
const export_name = try wasm.string_table.put(wasm.base.allocator, mod.intern_pool.stringToSlice(exp.opts.name));
if (wasm.globals.getPtr(export_name)) |existing_loc| {
if (existing_loc.index == atom.sym_index) continue;
const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
const exp_is_weak = exp.opts.linkage == .Internal or exp.opts.linkage == .Weak;
// When both the to-be-exported symbol and the already existing symbol
// are strong symbols, we have a linker error.
// In the other case we replace one with the other.
if (!exp_is_weak and !existing_sym.isWeak()) {
try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
gpa,
decl.srcLoc(mod),
\\LinkError: symbol '{}' defined multiple times
\\ first definition in '{s}'
\\ next definition in '{s}'
,
.{ exp.opts.name.fmt(&mod.intern_pool), wasm.name, wasm.name },
));
continue;
} else if (exp_is_weak) {
continue; // to-be-exported symbol is weak, so we keep the existing symbol
} else {
// TODO: Revisit this, why was this needed?
existing_loc.index = atom.sym_index;
existing_loc.file = null;
// exp.link.wasm.sym_index = existing_loc.index;
}
}
const exported_atom_index = try wasm.getOrCreateAtomForDecl(exp.exported_decl);
const exported_atom = wasm.getAtom(exported_atom_index);
const export_name = try wasm.string_table.put(wasm.base.allocator, mod.intern_pool.stringToSlice(exp.opts.name));
const sym_loc = exported_atom.symbolLoc();
const symbol = sym_loc.getSymbol(wasm);
symbol.setGlobal(true);
symbol.setUndefined(false);
symbol.index = atom_sym.index;
symbol.tag = atom_sym.tag;
symbol.name = atom_sym.name;
switch (exp.opts.linkage) {
.Internal => {
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
symbol.setFlag(.WASM_SYM_BINDING_WEAK);
},
.Weak => {
symbol.setFlag(.WASM_SYM_BINDING_WEAK);
@ -1768,22 +1747,52 @@ pub fn updateDeclExports(
continue;
},
}
if (wasm.globals.get(export_name)) |existing_loc| {
if (existing_loc.index == atom.sym_index) continue;
const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
if (!existing_sym.isUndefined()) blk: {
if (symbol.isWeak()) {
try wasm.discarded.put(wasm.base.allocator, existing_loc, sym_loc);
continue; // to-be-exported symbol is weak, so we keep the existing symbol
}
// new symbol is not weak while existing is, replace existing symbol
if (existing_sym.isWeak()) {
break :blk;
}
// When both the to-be-exported symbol and the already existing symbol
// are strong symbols, we have a linker error.
// In the other case we replace one with the other.
try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
gpa,
decl.srcLoc(mod),
\\LinkError: symbol '{}' defined multiple times
\\ first definition in '{s}'
\\ next definition in '{s}'
,
.{ exp.opts.name.fmt(&mod.intern_pool), wasm.name, wasm.name },
));
continue;
}
// in this case the existing symbol must be replaced either because it's weak or undefined.
try wasm.discarded.put(wasm.base.allocator, existing_loc, sym_loc);
_ = wasm.imports.remove(existing_loc);
_ = wasm.undefs.swapRemove(existing_sym.name);
}
// Ensure the symbol will be exported using the given name
if (!mod.intern_pool.stringEqlSlice(exp.opts.name, sym_loc.getName(wasm))) {
try wasm.export_names.put(wasm.base.allocator, sym_loc, export_name);
}
symbol.setGlobal(true);
symbol.setUndefined(false);
try wasm.globals.put(
wasm.base.allocator,
export_name,
sym_loc,
);
// if the symbol was previously undefined, remove it as an import
_ = wasm.imports.remove(sym_loc);
_ = wasm.undefs.swapRemove(export_name);
}
}
@ -1900,6 +1909,17 @@ pub fn addOrUpdateImport(
global_gop.value_ptr.* = loc;
try wasm.resolved_symbols.put(wasm.base.allocator, loc, {});
try wasm.undefs.putNoClobber(wasm.base.allocator, decl_name_index, loc);
} else if (global_gop.value_ptr.*.index != symbol_index) {
// We are not updating a symbol, but found an existing global
// symbol with the same name. This means we always favor the
// existing symbol, regardless whether it's defined or not.
// We can also skip storing the import as we will not output
// this symbol.
return wasm.discarded.put(
wasm.base.allocator,
.{ .file = null, .index = symbol_index },
global_gop.value_ptr.*,
);
}
if (type_index) |ty_index| {
@ -1915,8 +1935,8 @@ pub fn addOrUpdateImport(
};
}
} else {
// non-functions will not be imported from the runtime, but only resolved during link-time
symbol.tag = .data;
return; // non-functions will not be imported from the runtime, but only resolved during link-time
}
}

View File

@ -158,6 +158,7 @@ test {
_ = @import("behavior/enum.zig");
_ = @import("behavior/error.zig");
_ = @import("behavior/eval.zig");
_ = @import("behavior/export_self_referential_type_info.zig");
_ = @import("behavior/field_parent_ptr.zig");
_ = @import("behavior/floatop.zig");
_ = @import("behavior/fn.zig");
@ -241,7 +242,6 @@ test {
if (builtin.zig_backend != .stage2_arm and
builtin.zig_backend != .stage2_x86_64 and
builtin.zig_backend != .stage2_aarch64 and
builtin.zig_backend != .stage2_wasm and
builtin.zig_backend != .stage2_c and
builtin.zig_backend != .stage2_spirv64)
{
@ -250,8 +250,4 @@ test {
_ = @import("behavior/bugs/14198.zig");
_ = @import("behavior/export.zig");
}
if (builtin.zig_backend != .stage2_wasm) {
_ = @import("behavior/export_self_referential_type_info.zig");
}
}

View File

@ -17,7 +17,6 @@ test "union that needs padding bytes inside an array" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
var as = [_]A{
A{ .B = B{ .D = 1 } },

View File

@ -11,7 +11,6 @@ comptime {
const builtin = @import("builtin");
test "issue 529 fixed" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO