Commit c535422423

Andrew Kelley <andrew@ziglang.org>
2025-01-14 09:55:55
wasm linker: implement hidden visibility
1 parent f89ef2f
Changed files (6)
src/link/Wasm/Flush.zig
@@ -164,10 +164,14 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
             }
         }
 
-        for (wasm.nav_exports.keys()) |*nav_export| {
+        for (wasm.nav_exports.keys(), wasm.nav_exports.values()) |*nav_export, export_index| {
             if (ip.isFunctionType(ip.getNav(nav_export.nav_index).typeOf(ip))) {
                 log.debug("flush export '{s}' nav={d}", .{ nav_export.name.slice(wasm), nav_export.nav_index });
-                try wasm.function_exports.put(gpa, nav_export.name, Wasm.FunctionIndex.fromIpNav(wasm, nav_export.nav_index).?);
+                const function_index = Wasm.FunctionIndex.fromIpNav(wasm, nav_export.nav_index).?;
+                switch (export_index.ptr(zcu).opts.visibility) {
+                    .default, .protected => try wasm.function_exports.put(gpa, nav_export.name, function_index),
+                    .hidden => try wasm.hidden_function_exports.put(gpa, nav_export.name, function_index),
+                }
                 _ = f.missing_exports.swapRemove(nav_export.name);
                 _ = f.function_imports.swapRemove(nav_export.name);
 
src/link/Wasm.zig
@@ -210,8 +210,7 @@ entry_resolution: FunctionImport.Resolution = .unresolved,
 
 /// Empty when outputting an object.
 function_exports: std.AutoArrayHashMapUnmanaged(String, FunctionIndex) = .empty,
-/// Tracks the value at the end of prelink.
-function_exports_len: u32 = 0,
+hidden_function_exports: std.AutoArrayHashMapUnmanaged(String, FunctionIndex) = .empty,
 global_exports: std.ArrayListUnmanaged(GlobalExport) = .empty,
 /// Tracks the value at the end of prelink.
 global_exports_len: u32 = 0,
@@ -360,9 +359,8 @@ pub const FunctionIndex = enum(u32) {
         if (wasm.object_function_imports.getPtr(name)) |import| {
             return fromResolution(wasm, import.resolution);
         }
-        if (wasm.function_exports.get(name)) |index| {
-            return index;
-        }
+        if (wasm.function_exports.get(name)) |index| return index;
+        if (wasm.hidden_function_exports.get(name)) |index| return index;
         return null;
     }
 
@@ -1919,8 +1917,11 @@ pub const DataSegmentId = enum(u32) {
                 const zcu = wasm.base.comp.zcu.?;
                 const ip = &zcu.intern_pool;
                 const nav = ip.getNav(i.key(wasm).*);
-                return nav.getLinkSection().toSlice(ip) orelse
-                    if (nav.isThreadlocal(ip)) ".tdata" else ".data";
+                return nav.getLinkSection().toSlice(ip) orelse switch (category(id, wasm)) {
+                    .tls => ".tdata",
+                    .data => ".data",
+                    .zero => ".bss",
+                };
             },
         };
     }
@@ -3110,6 +3111,7 @@ pub fn deinit(wasm: *Wasm) void {
 
     wasm.func_types.deinit(gpa);
     wasm.function_exports.deinit(gpa);
+    wasm.hidden_function_exports.deinit(gpa);
     wasm.function_imports.deinit(gpa);
     wasm.functions.deinit(gpa);
     wasm.globals.deinit(gpa);
@@ -3417,7 +3419,6 @@ pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!v
         }
     }
     wasm.functions_end_prelink = @intCast(wasm.functions.entries.len);
-    wasm.function_exports_len = @intCast(wasm.function_exports.entries.len);
 
     for (wasm.object_global_imports.keys(), wasm.object_global_imports.values(), 0..) |name, *import, i| {
         if (import.flags.isIncluded(rdynamic)) {
@@ -3491,8 +3492,14 @@ fn markFunction(wasm: *Wasm, i: ObjectFunctionIndex) link.File.FlushError!void {
     const function = i.ptr(wasm);
     markObject(wasm, function.object_index);
 
-    if (!is_obj and function.flags.isExported(rdynamic))
-        try wasm.function_exports.put(gpa, function.name.unwrap().?, @enumFromInt(gop.index));
+    if (!is_obj and function.flags.isExported(rdynamic)) {
+        const symbol_name = function.name.unwrap().?;
+        if (function.flags.visibility_hidden) {
+            try wasm.hidden_function_exports.put(gpa, symbol_name, @enumFromInt(gop.index));
+        } else {
+            try wasm.function_exports.put(gpa, symbol_name, @enumFromInt(gop.index));
+        }
+    }
 
     try wasm.markRelocations(function.relocations(wasm));
 }
@@ -3778,6 +3785,9 @@ pub fn flushModule(
     const function_exports_end_zcu: u32 = @intCast(wasm.function_exports.entries.len);
     defer wasm.function_exports.shrinkRetainingCapacity(function_exports_end_zcu);
 
+    const hidden_function_exports_end_zcu: u32 = @intCast(wasm.hidden_function_exports.entries.len);
+    defer wasm.hidden_function_exports.shrinkRetainingCapacity(hidden_function_exports_end_zcu);
+
     wasm.flush_buffer.clear();
     try wasm.flush_buffer.missing_exports.reinit(gpa, wasm.missing_exports.keys(), &.{});
     try wasm.flush_buffer.function_imports.reinit(gpa, wasm.function_imports.keys(), wasm.function_imports.values());
src/Compilation.zig
@@ -3066,7 +3066,7 @@ pub fn saveState(comp: *Compilation) !void {
         .wasm => {
             const wasm = lf.cast(.wasm).?;
             const is_obj = comp.config.output_mode == .Obj;
-            try bufs.ensureUnusedCapacity(83);
+            try bufs.ensureUnusedCapacity(85);
             addBuf(&bufs, wasm.string_bytes.items);
             // TODO make it well-defined memory layout
             //addBuf(&bufs, mem.sliceAsBytes(wasm.objects.items));
@@ -3133,6 +3133,8 @@ pub fn saveState(comp: *Compilation) !void {
             addBuf(&bufs, mem.sliceAsBytes(wasm.missing_exports.keys()));
             addBuf(&bufs, mem.sliceAsBytes(wasm.function_exports.keys()));
             addBuf(&bufs, mem.sliceAsBytes(wasm.function_exports.values()));
+            addBuf(&bufs, mem.sliceAsBytes(wasm.hidden_function_exports.keys()));
+            addBuf(&bufs, mem.sliceAsBytes(wasm.hidden_function_exports.values()));
             addBuf(&bufs, mem.sliceAsBytes(wasm.global_exports.items));
             addBuf(&bufs, mem.sliceAsBytes(wasm.functions.keys()));
             addBuf(&bufs, mem.sliceAsBytes(wasm.function_imports.keys()));
test/link/wasm/bss/build.zig
@@ -47,16 +47,14 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt
         check_lib.checkInHeaders();
         check_lib.checkExact("Section custom");
         check_lib.checkExact("type data_segment");
-        check_lib.checkExact("names 2");
-        check_lib.checkExact("index 0");
-        check_lib.checkExact("name .rodata");
+        check_lib.checkExact("names 1");
         // for safe optimization modes `undefined` is stored in data instead of bss.
         if (is_safe) {
-            check_lib.checkExact("index 1");
+            check_lib.checkExact("index 0");
             check_lib.checkExact("name .data");
             check_lib.checkNotPresent("name .bss");
         } else {
-            check_lib.checkExact("index 1"); // bss section always last
+            check_lib.checkExact("index 0"); // bss section always last
             check_lib.checkExact("name .bss");
         }
         test_step.dependOn(&check_lib.step);
@@ -84,10 +82,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt
         check_lib.checkInHeaders();
         check_lib.checkExact("Section custom");
         check_lib.checkExact("type data_segment");
-        check_lib.checkExact("names 2");
+        check_lib.checkExact("names 1");
         check_lib.checkExact("index 0");
-        check_lib.checkExact("name .rodata");
-        check_lib.checkExact("index 1");
         check_lib.checkExact("name .bss");
 
         test_step.dependOn(&check_lib.step);
test/link/wasm/bss/lib.zig
@@ -1,5 +1,9 @@
 pub var bss: u32 = undefined;
 
-export fn foo() void {
-    _ = bss;
+fn foo() callconv(.c) u32 {
+    return bss;
+}
+
+comptime {
+    @export(&foo, .{ .name = "foo", .visibility = .hidden });
 }
test/link/wasm/bss/lib2.zig
@@ -1,5 +1,9 @@
 pub var bss: u32 = 0;
 
-export fn foo() void {
-    _ = bss;
+fn foo() callconv(.c) u32 {
+    return bss;
+}
+
+comptime {
+    @export(&foo, .{ .name = "foo", .visibility = .hidden });
 }