wasm-linker: Implement symbol names emitting

The linker will now emit names for all function, global and data segment symbols.
This increases the ability to debug wasm modules tremendously as tools like wasm2wat
can use this information to generate named functions, globals etc, rather than placeholders such as $f1.
This commit is contained in:
Luuk de Gram 2021-12-22 17:39:44 +01:00 committed by Jakub Konka
parent 3bd0575cfe
commit e061d75cdf
3 changed files with 105 additions and 5 deletions

View File

@ -406,6 +406,22 @@ pub fn externalKind(val: ExternalKind) u8 {
return @enumToInt(val);
}
/// Defines the enum values for each subsection id for the "Names" custom section
/// as described by:
/// https://webassembly.github.io/spec/core/appendix/custom.html?highlight=name#name-section
pub const NameSubsection = enum(u8) {
module,
function,
local,
label,
type,
table,
memory,
global,
elem_segment,
data_segment,
};
// type constants
pub const element_type: u8 = 0x70;
pub const function_type: u8 = 0x60;

View File

@ -328,6 +328,7 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
self.symbols_free_list.append(self.base.allocator, atom.sym_index) catch {};
atom.deinit(self.base.allocator);
_ = self.decls.remove(decl);
self.symbols.items[atom.sym_index].tag = .dead; // to ensure it does not end in the names section
if (decl.isExtern()) {
const import = self.imports.fetchRemove(decl.link.wasm.sym_index).?.value;
@ -336,11 +337,6 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
else => unreachable,
}
}
// maybe remove from function table if needed
if (decl.ty.zigTypeTag() == .Fn) {
_ = self.function_table.remove(atom.sym_index);
}
}
/// Appends a new entry to the indirect function table
@ -915,6 +911,75 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
@intCast(u32, segment_count),
);
}
// Custom section "name" which contains symbol names
{
const Name = struct {
index: u32,
name: []const u8,
fn lessThan(context: void, lhs: @This(), rhs: @This()) bool {
_ = context;
return lhs.index < rhs.index;
}
};
var funcs = try std.ArrayList(Name).initCapacity(self.base.allocator, self.functions.items.len + self.imported_functions_count);
defer funcs.deinit();
var globals = try std.ArrayList(Name).initCapacity(self.base.allocator, self.globals.items.len);
defer globals.deinit();
var segments = try std.ArrayList(Name).initCapacity(self.base.allocator, self.data_segments.count());
defer segments.deinit();
for (self.symbols.items) |symbol| {
switch (symbol.tag) {
.function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = std.mem.sliceTo(symbol.name, 0) }),
.global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = std.mem.sliceTo(symbol.name, 0) }),
else => {},
}
}
// data segments are already 'ordered'
for (self.data_segments.keys()) |key, index| {
segments.appendAssumeCapacity(.{ .index = @intCast(u32, index), .name = key });
}
std.sort.sort(Name, funcs.items, {}, Name.lessThan);
std.sort.sort(Name, globals.items, {}, Name.lessThan);
const header_offset = try reserveCustomSectionHeader(file);
const writer = file.writer();
try leb.writeULEB128(writer, @intCast(u32, "name".len));
try writer.writeAll("name");
try self.emitNameSubsection(.function, funcs.items, writer);
try self.emitNameSubsection(.global, globals.items, writer);
try self.emitNameSubsection(.data_segment, segments.items, writer);
try writeCustomSectionHeader(
file,
header_offset,
@intCast(u32, (try file.getPos()) - header_offset - header_size),
);
}
}
fn emitNameSubsection(self: *Wasm, section_id: std.wasm.NameSubsection, names: anytype, writer: anytype) !void {
// We must emit subsection size, so first write to a temporary list
var section_list = std.ArrayList(u8).init(self.base.allocator);
defer section_list.deinit();
const sub_writer = section_list.writer();
try leb.writeULEB128(sub_writer, @intCast(u32, names.len));
for (names) |name| {
try leb.writeULEB128(sub_writer, name.index);
try leb.writeULEB128(sub_writer, @intCast(u32, name.name.len));
try sub_writer.writeAll(name.name);
}
// From now, write to the actual writer
try leb.writeULEB128(writer, @enumToInt(section_id));
try leb.writeULEB128(writer, @intCast(u32, section_list.items.len));
try writer.writeAll(section_list.items);
}
fn emitLimits(writer: anytype, limits: wasm.Limits) !void {
@ -1335,6 +1400,15 @@ fn reserveVecSectionHeader(file: fs.File) !u64 {
return (try file.getPos()) - header_size;
}
fn reserveCustomSectionHeader(file: fs.File) !u64 {
// unlike regular section, we don't emit the count
const header_size = 1 + 5;
// TODO: this should be a single lseek(2) call, but fs.File does not
// currently provide a way to do this.
try file.seekBy(header_size);
return (try file.getPos()) - header_size;
}
fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size: u32, items: u32) !void {
var buf: [1 + 5 + 5]u8 = undefined;
buf[0] = @enumToInt(section);
@ -1343,6 +1417,13 @@ fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size
try file.pwriteAll(&buf, offset);
}
fn writeCustomSectionHeader(file: fs.File, offset: u64, size: u32) !void {
var buf: [1 + 5]u8 = undefined;
buf[0] = 0; // 0 = 'custom' section
leb.writeUnsignedFixed(5, buf[1..6], size);
try file.pwriteAll(&buf, offset);
}
/// Searches for an a matching function signature, when not found
/// a new entry will be made. The index of the existing/new signature will be returned.
pub fn putOrGetFuncType(self: *Wasm, func_type: wasm.Type) !u32 {

View File

@ -25,6 +25,9 @@ pub const Tag = enum {
section,
event,
table,
/// synthetic kind used by the wasm linker during incremental compilation
/// to notate a symbol has been freed, but still lives in the symbol list.
dead,
/// From a given symbol tag, returns the `ExternalType`
/// Asserts the given tag can be represented as an external type.