From e061d75cdf6a7994dd50f2d28c9f1ed3ed5ec205 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 22 Dec 2021 17:39:44 +0100 Subject: [PATCH] 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. --- lib/std/wasm.zig | 16 +++++++ src/link/Wasm.zig | 91 +++++++++++++++++++++++++++++++++++++--- src/link/Wasm/Symbol.zig | 3 ++ 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig index f96c1bc1b9..681ac405fc 100644 --- a/lib/std/wasm.zig +++ b/lib/std/wasm.zig @@ -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; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index c317c000a1..07ca28202a 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -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 { diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index 0fe4163f27..0f2247f5d1 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -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.