Commit d6b42e585b

Andrew Kelley <andrew@ziglang.org>
2024-12-16 04:55:40
wasm linker: implement name subsection
unlike the previous implementation, we can simply iterate an array.
1 parent e80a203
Changed files (4)
src/link/Wasm/Flush.zig
@@ -52,7 +52,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void {
     f.* = undefined;
 }
 
-pub fn finish(f: *Flush, wasm: *Wasm, arena: Allocator) !void {
+pub fn finish(f: *Flush, wasm: *Wasm) !void {
     const comp = wasm.base.comp;
     const shared_memory = comp.config.shared_memory;
     const diags = &comp.link_diags;
@@ -685,7 +685,7 @@ pub fn finish(f: *Flush, wasm: *Wasm, arena: Allocator) !void {
         //        try wasm.emitDataRelocations(binary_bytes, data_index, symbol_table);
         //}
     } else if (comp.config.debug_format != .strip) {
-        try emitNameSection(wasm, binary_bytes, arena);
+        try emitNameSection(wasm, &f.data_segments, binary_bytes);
     }
 
     if (comp.config.debug_format != .strip) {
@@ -732,117 +732,77 @@ pub fn finish(f: *Flush, wasm: *Wasm, arena: Allocator) !void {
     try file.setEndPos(binary_bytes.items.len);
 }
 
-fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayListUnmanaged(u8), arena: Allocator) !void {
-    if (true) {
-        std.log.warn("TODO emit name section", .{});
-        return;
-    }
+fn emitNameSection(
+    wasm: *Wasm,
+    data_segments: *const std.AutoArrayHashMapUnmanaged(Wasm.DataSegment.Index, u32),
+    binary_bytes: *std.ArrayListUnmanaged(u8),
+) !void {
     const comp = wasm.base.comp;
     const gpa = comp.gpa;
-    const import_memory = comp.config.import_memory;
 
-    // Deduplicate symbols that point to the same function.
-    var funcs: std.AutoArrayHashMapUnmanaged(u32, String) = .empty;
-    try funcs.ensureUnusedCapacityPrecise(arena, wasm.functions.count() + wasm.function_imports.items.len);
+    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+    defer writeCustomSectionHeader(binary_bytes, header_offset);
 
-    const NamedIndex = struct {
-        index: u32,
-        name: String,
-    };
+    const name_name = "name";
+    try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, name_name.len));
+    try binary_bytes.appendSlice(gpa, name_name);
 
-    var globals: std.MultiArrayList(NamedIndex) = .empty;
-    try globals.ensureTotalCapacityPrecise(arena, wasm.globals.items.len + wasm.global_imports.items.len);
-
-    var segments: std.MultiArrayList(NamedIndex) = .empty;
-    try segments.ensureTotalCapacityPrecise(arena, wasm.data_segments.count());
-
-    for (wasm.resolved_symbols.keys()) |sym_loc| {
-        const symbol = wasm.finalSymbolByLoc(sym_loc).*;
-        if (!symbol.flags.alive) continue;
-        const name = wasm.finalSymbolByLoc(sym_loc).name;
-        switch (symbol.tag) {
-            .function => {
-                const index = if (symbol.flags.undefined)
-                    @intFromEnum(symbol.pointee.function_import)
-                else
-                    wasm.function_imports.items.len + @intFromEnum(symbol.pointee.function);
-                const gop = funcs.getOrPutAssumeCapacity(index);
-                if (gop.found_existing) {
-                    assert(gop.value_ptr.* == name);
-                } else {
-                    gop.value_ptr.* = name;
-                }
-            },
-            .global => {
-                globals.appendAssumeCapacity(.{
-                    .index = if (symbol.flags.undefined)
-                        @intFromEnum(symbol.pointee.global_import)
-                    else
-                        @intFromEnum(symbol.pointee.global),
-                    .name = name,
-                });
-            },
-            else => {},
+    {
+        const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+        defer replaceHeader(binary_bytes, sub_offset, @intFromEnum(std.wasm.NameSubsection.function));
+
+        const total_functions: u32 = @intCast(wasm.function_imports.entries.len + wasm.functions.entries.len);
+        try leb.writeUleb128(binary_bytes.writer(gpa), total_functions);
+
+        for (wasm.function_imports.keys(), 0..) |name_index, function_index| {
+            const name = name_index.slice(wasm);
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(function_index)));
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(name.len)));
+            try binary_bytes.appendSlice(gpa, name);
+        }
+        for (wasm.functions.keys(), wasm.function_imports.entries.len..) |resolution, function_index| {
+            const name = resolution.name(wasm).?;
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(function_index)));
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(name.len)));
+            try binary_bytes.appendSlice(gpa, name);
         }
     }
 
-    for (wasm.data_segments.keys(), 0..) |key, index| {
-        // bss section is not emitted when this condition holds true, so we also
-        // do not output a name for it.
-        if (!import_memory and mem.eql(u8, key, ".bss")) continue;
-        segments.appendAssumeCapacity(.{ .index = @intCast(index), .name = key });
-    }
+    {
+        const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+        defer replaceHeader(binary_bytes, sub_offset, @intFromEnum(std.wasm.NameSubsection.global));
 
-    const Sort = struct {
-        indexes: []const u32,
-        pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
-            return ctx.indexes[lhs] < ctx.indexes[rhs];
+        const total_globals: u32 = @intCast(wasm.global_imports.entries.len + wasm.globals.entries.len);
+        try leb.writeUleb128(binary_bytes.writer(gpa), total_globals);
+
+        for (wasm.global_imports.keys(), 0..) |name_index, global_index| {
+            const name = name_index.slice(wasm);
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(global_index)));
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(name.len)));
+            try binary_bytes.appendSlice(gpa, name);
         }
-    };
-    funcs.entries.sortUnstable(@as(Sort, .{ .indexes = funcs.keys() }));
-    globals.sortUnstable(@as(Sort, .{ .indexes = globals.items(.index) }));
-    // Data segments are already ordered.
+        for (wasm.globals.keys(), wasm.global_imports.entries.len..) |resolution, global_index| {
+            const name = resolution.name(wasm).?;
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(global_index)));
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(name.len)));
+            try binary_bytes.appendSlice(gpa, name);
+        }
+    }
 
-    const header_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
-    defer writeCustomSectionHeader(binary_bytes, header_offset);
+    {
+        const sub_offset = try reserveCustomSectionHeader(gpa, binary_bytes);
+        defer replaceHeader(binary_bytes, sub_offset, @intFromEnum(std.wasm.NameSubsection.data_segment));
 
-    const writer = binary_bytes.writer(gpa);
-    try leb.writeUleb128(writer, @as(u32, @intCast("name".len)));
-    try writer.writeAll("name");
+        const total_globals: u32 = @intCast(wasm.global_imports.entries.len + wasm.globals.entries.len);
+        try leb.writeUleb128(binary_bytes.writer(gpa), total_globals);
 
-    try emitNameSubsection(wasm, binary_bytes, .function, funcs.keys(), funcs.values());
-    try emitNameSubsection(wasm, binary_bytes, .global, globals.items(.index), globals.items(.name));
-    try emitNameSubsection(wasm, binary_bytes, .data_segment, segments.items(.index), segments.items(.name));
-}
-
-fn emitNameSubsection(
-    wasm: *const Wasm,
-    binary_bytes: *std.ArrayListUnmanaged(u8),
-    section_id: std.wasm.NameSubsection,
-    indexes: []const u32,
-    names: []const String,
-) !void {
-    assert(indexes.len == names.len);
-    const gpa = wasm.base.comp.gpa;
-    // We must emit subsection size, so first write to a temporary list
-    var section_list: std.ArrayListUnmanaged(u8) = .empty;
-    defer section_list.deinit(gpa);
-    const sub_writer = section_list.writer(gpa);
-
-    try leb.writeUleb128(sub_writer, @as(u32, @intCast(names.len)));
-    for (indexes, names) |index, name_index| {
-        const name = name_index.slice(wasm);
-        log.debug("emit symbol '{s}' type({s})", .{ name, @tagName(section_id) });
-        try leb.writeUleb128(sub_writer, index);
-        try leb.writeUleb128(sub_writer, @as(u32, @intCast(name.len)));
-        try sub_writer.writeAll(name);
+        for (data_segments.keys(), 0..) |ds, i| {
+            const name = ds.ptr(wasm).name.slice(wasm).?;
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(i)));
+            try leb.writeUleb128(binary_bytes.writer(gpa), @as(u32, @intCast(name.len)));
+            try binary_bytes.appendSlice(gpa, name);
+        }
     }
-
-    // From now, write to the actual writer
-    const writer = binary_bytes.writer(gpa);
-    try leb.writeUleb128(writer, @intFromEnum(section_id));
-    try leb.writeUleb128(writer, @as(u32, @intCast(section_list.items.len)));
-    try binary_bytes.appendSlice(gpa, section_list.items);
 }
 
 fn emitFeaturesSection(
@@ -1094,11 +1054,15 @@ fn reserveCustomSectionHeader(gpa: Allocator, bytes: *std.ArrayListUnmanaged(u8)
 }
 
 fn writeCustomSectionHeader(bytes: *std.ArrayListUnmanaged(u8), offset: u32) void {
+    return replaceHeader(bytes, offset, 0); // 0 = 'custom' section
+}
+
+fn replaceHeader(bytes: *std.ArrayListUnmanaged(u8), offset: u32, tag: u8) void {
     const size: u32 = @intCast(bytes.items.len - offset - section_header_size);
     var buf: [section_header_size]u8 = undefined;
     var fbw = std.io.fixedBufferStream(&buf);
     const w = fbw.writer();
-    w.writeByte(0) catch unreachable; // 0 = 'custom' section
+    w.writeByte(tag) catch unreachable;
     leb.writeUleb128(w, size) catch unreachable;
     bytes.replaceRangeAssumeCapacity(offset, section_header_size, fbw.getWritten());
 }
src/link/Wasm.zig
@@ -550,7 +550,7 @@ pub const NavObj = extern struct {
     /// Empty if not emitting an object.
     relocs: OutReloc.Slice,
 
-    /// Index into `navs`.
+    /// Index into `Wasm.navs_obj`.
     /// Note that swapRemove is sometimes performed on `navs`.
     pub const Index = enum(u32) {
         _,
@@ -562,13 +562,20 @@ pub const NavObj = extern struct {
         pub fn value(i: @This(), wasm: *const Wasm) *NavObj {
             return &wasm.navs_obj.values()[@intFromEnum(i)];
         }
+
+        pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
+            const zcu = wasm.base.comp.zcu.?;
+            const ip = &zcu.intern_pool;
+            const nav = ip.getNav(i.key(wasm).*);
+            return nav.fqn.toSlice(ip);
+        }
     };
 };
 
 pub const NavExe = extern struct {
     code: DataSegment.Payload,
 
-    /// Index into `navs`.
+    /// Index into `Wasm.navs_exe`.
     /// Note that swapRemove is sometimes performed on `navs`.
     pub const Index = enum(u32) {
         _,
@@ -580,6 +587,13 @@ pub const NavExe = extern struct {
         pub fn value(i: @This(), wasm: *const Wasm) *NavExe {
             return &wasm.navs_exe.values()[@intFromEnum(i)];
         }
+
+        pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
+            const zcu = wasm.base.comp.zcu.?;
+            const ip = &zcu.intern_pool;
+            const nav = ip.getNav(i.key(wasm).*);
+            return nav.fqn.toSlice(ip);
+        }
     };
 };
 
@@ -598,6 +612,14 @@ pub const ZcuFunc = extern struct {
         pub fn value(i: @This(), wasm: *const Wasm) *ZcuFunc {
             return &wasm.zcu_funcs.values()[@intFromEnum(i)];
         }
+
+        pub fn name(i: @This(), wasm: *const Wasm) [:0]const u8 {
+            const zcu = wasm.base.comp.zcu.?;
+            const ip = &zcu.intern_pool;
+            const func = ip.toFunc(i.key(wasm).*);
+            const nav = ip.getNav(func.owner_nav);
+            return nav.fqn.toSlice(ip);
+        }
     };
 };
 
@@ -720,6 +742,19 @@ pub const FunctionImport = extern struct {
                 .zcu_func => @panic("TODO"),
             };
         }
+
+        pub fn name(r: Resolution, wasm: *const Wasm) ?[]const u8 {
+            return switch (unpack(r, wasm)) {
+                .unresolved => unreachable,
+                .__wasm_apply_global_tls_relocs => @tagName(Unpacked.__wasm_apply_global_tls_relocs),
+                .__wasm_call_ctors => @tagName(Unpacked.__wasm_call_ctors),
+                .__wasm_init_memory => @tagName(Unpacked.__wasm_init_memory),
+                .__wasm_init_tls => @tagName(Unpacked.__wasm_init_tls),
+                .__zig_error_names => @tagName(Unpacked.__zig_error_names),
+                .object_function => |i| i.ptr(wasm).name.slice(wasm),
+                .zcu_func => |i| i.name(wasm),
+            };
+        }
     };
 
     /// Index into `object_function_imports`.
@@ -851,6 +886,22 @@ pub const GlobalImport = extern struct {
                 .nav_exe = @enumFromInt(wasm.navs_exe.getIndex(ip_nav).?),
             });
         }
+
+        pub fn name(r: Resolution, wasm: *const Wasm) ?[]const u8 {
+            return switch (unpack(r, wasm)) {
+                .unresolved => unreachable,
+                .__heap_base => @tagName(Unpacked.__heap_base),
+                .__heap_end => @tagName(Unpacked.__heap_end),
+                .__stack_pointer => @tagName(Unpacked.__stack_pointer),
+                .__tls_align => @tagName(Unpacked.__tls_align),
+                .__tls_base => @tagName(Unpacked.__tls_base),
+                .__tls_size => @tagName(Unpacked.__tls_size),
+                .__zig_error_name_table => @tagName(Unpacked.__zig_error_name_table),
+                .object_global => |i| i.name(wasm).slice(wasm),
+                .nav_obj => |i| i.name(wasm),
+                .nav_exe => |i| i.name(wasm),
+            };
+        }
     };
 
     /// Index into `Wasm.object_global_imports`.
@@ -1038,6 +1089,10 @@ pub const ObjectGlobalIndex = enum(u32) {
     pub fn ptr(index: ObjectGlobalIndex, wasm: *const Wasm) *Global {
         return &wasm.object_globals.items[@intFromEnum(index)];
     }
+
+    pub fn name(index: ObjectGlobalIndex, wasm: *const Wasm) OptionalString {
+        return index.ptr(wasm).name;
+    }
 };
 
 /// Index into `Wasm.object_memories`.
@@ -1049,7 +1104,7 @@ pub const ObjectMemoryIndex = enum(u32) {
     }
 };
 
-/// Index into `object_functions`.
+/// Index into `Wasm.object_functions`.
 pub const ObjectFunctionIndex = enum(u32) {
     _,
 
@@ -2397,7 +2452,7 @@ pub fn flushModule(
     defer sub_prog_node.end();
 
     wasm.flush_buffer.clear();
-    return wasm.flush_buffer.finish(wasm, arena) catch |err| switch (err) {
+    return wasm.flush_buffer.finish(wasm) catch |err| switch (err) {
         error.OutOfMemory => return error.OutOfMemory,
         error.LinkFailure => return error.LinkFailure,
         else => |e| return diags.fail("failed to flush wasm: {s}", .{@errorName(e)}),
src/InternPool.zig
@@ -11801,6 +11801,10 @@ pub fn toEnum(ip: *const InternPool, comptime E: type, i: Index) E {
     return @enumFromInt(ip.indexToKey(int).int.storage.u64);
 }
 
+pub fn toFunc(ip: *const InternPool, i: Index) Key.Func {
+    return ip.indexToKey(i).func;
+}
+
 pub fn aggregateTypeLen(ip: *const InternPool, ty: Index) u64 {
     return switch (ip.indexToKey(ty)) {
         .struct_type => ip.loadStructType(ty).field_types.len,
src/Zcu.zig
@@ -3483,7 +3483,7 @@ pub fn iesFuncIndex(zcu: *const Zcu, ies_index: InternPool.Index) InternPool.Ind
 }
 
 pub fn funcInfo(zcu: *const Zcu, func_index: InternPool.Index) InternPool.Key.Func {
-    return zcu.intern_pool.indexToKey(func_index).func;
+    return zcu.intern_pool.toFunc(func_index);
 }
 
 pub fn toEnum(zcu: *const Zcu, comptime E: type, val: Value) E {