Commit 4cb2f11693

Luuk de Gram <luuk@degram.dev>
2021-12-21 20:10:36
wasm-linker: Implement the --export-table and --import-table flags.
This implements the flags for both the linker frontend as well as the self-hosted linker. Closes #5790
1 parent e15a267
Changed files (5)
lib/std/build.zig
@@ -1479,6 +1479,8 @@ pub const LibExeObjStep = struct {
     sanitize_thread: bool,
     rdynamic: bool,
     import_memory: bool = false,
+    import_table: bool = false,
+    export_table: bool = false,
     initial_memory: ?u64 = null,
     max_memory: ?u64 = null,
     global_base: ?u64 = null,
@@ -2509,6 +2511,12 @@ pub const LibExeObjStep = struct {
         if (self.import_memory) {
             try zig_args.append("--import-memory");
         }
+        if (self.import_table) {
+            try zig_args.append("--import-table");
+        }
+        if (self.export_table) {
+            try zig_args.append("--export-table");
+        }
         if (self.initial_memory) |initial_memory| {
             try zig_args.append(builder.fmt("--initial-memory={d}", .{initial_memory}));
         }
src/link/Wasm.zig
@@ -635,11 +635,30 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
     }
 
     // Import section
-    const import_mem = self.base.options.import_memory;
-    if (self.imports.count() != 0 or import_mem) {
+    const import_memory = self.base.options.import_memory;
+    const import_table = self.base.options.import_table;
+    if (self.imports.count() != 0 or import_memory or import_table) {
         const header_offset = try reserveVecSectionHeader(file);
         const writer = file.writer();
 
+        // import table is always first table so emit that first
+        if (import_table) {
+            const table_imp: wasm.Import = .{
+                .module_name = self.host_name,
+                .name = "__indirect_function_table",
+                .kind = .{
+                    .table = .{
+                        .limits = .{
+                            .min = @intCast(u32, self.imports.count()),
+                            .max = null,
+                        },
+                        .reftype = .funcref,
+                    },
+                },
+            };
+            try emitImport(writer, table_imp);
+        }
+
         var it = self.imports.iterator();
         while (it.next()) |entry| {
             const import_symbol = self.symbols.items[entry.key_ptr.*];
@@ -648,7 +667,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
             try emitImport(writer, import);
         }
 
-        if (import_mem) {
+        if (import_memory) {
             const mem_imp: wasm.Import = .{
                 .module_name = self.host_name,
                 .name = "memory",
@@ -662,7 +681,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
             header_offset,
             .import,
             @intCast(u32, (try file.getPos()) - header_offset - header_size),
-            @intCast(u32, self.imports.count() + @boolToInt(import_mem)),
+            @intCast(u32, self.imports.count() + @boolToInt(import_memory)),
         );
     }
 
@@ -684,7 +703,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
     }
 
     // Table section
-    if (self.function_table.count() > 0) {
+    const export_table = self.base.options.export_table;
+    if (!import_table and (self.function_table.count() > 0 or export_table)) {
         const header_offset = try reserveVecSectionHeader(file);
         const writer = file.writer();
 
@@ -767,7 +787,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
         }
 
         // export memory if size is not 0
-        if (!self.base.options.import_memory) {
+        if (!import_memory) {
             try leb.writeULEB128(writer, @intCast(u32, "memory".len));
             try writer.writeAll("memory");
             try writer.writeByte(wasm.externalKind(.memory));
@@ -775,6 +795,14 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
             count += 1;
         }
 
+        if (export_table) {
+            try leb.writeULEB128(writer, @intCast(u32, "__indirect_function_table".len));
+            try writer.writeAll("__indirect_function_table");
+            try writer.writeByte(wasm.externalKind(.table));
+            try leb.writeULEB128(writer, @as(u32, 0)); // function table is always the first table
+            count += 1;
+        }
+
         try writeVecSectionHeader(
             file,
             header_offset,
@@ -1008,6 +1036,8 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
         try man.addOptionalFile(compiler_rt_path);
         man.hash.addOptional(self.base.options.stack_size_override);
         man.hash.add(self.base.options.import_memory);
+        man.hash.add(self.base.options.import_table);
+        man.hash.add(self.base.options.export_table);
         man.hash.addOptional(self.base.options.initial_memory);
         man.hash.addOptional(self.base.options.max_memory);
         man.hash.addOptional(self.base.options.global_base);
@@ -1092,6 +1122,18 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
             try argv.append("--import-memory");
         }
 
+        if (self.base.options.import_table) {
+            if (self.base.options.export_table) {
+                log.err("--import-table and --export-table may not be used together", .{});
+                return error.InvalidArgs;
+            }
+            try argv.append("--import-table");
+        }
+
+        if (self.base.options.export_table) {
+            try argv.append("--export-table");
+        }
+
         if (self.base.options.initial_memory) |initial_memory| {
             const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory});
             try argv.append(arg);
src/Compilation.zig
@@ -724,6 +724,8 @@ pub const InitOptions = struct {
     linker_allow_shlib_undefined: ?bool = null,
     linker_bind_global_refs_locally: ?bool = null,
     linker_import_memory: ?bool = null,
+    linker_import_table: bool = false,
+    linker_export_table: bool = false,
     linker_initial_memory: ?u64 = null,
     linker_max_memory: ?u64 = null,
     linker_global_base: ?u64 = null,
@@ -1457,6 +1459,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             .allow_shlib_undefined = options.linker_allow_shlib_undefined,
             .bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false,
             .import_memory = options.linker_import_memory orelse false,
+            .import_table = options.linker_import_table,
+            .export_table = options.linker_export_table,
             .initial_memory = options.linker_initial_memory,
             .max_memory = options.linker_max_memory,
             .global_base = options.linker_global_base,
src/link.zig
@@ -102,6 +102,8 @@ pub const Options = struct {
     linker_optimization: u8,
     bind_global_refs_locally: bool,
     import_memory: bool,
+    import_table: bool,
+    export_table: bool,
     initial_memory: ?u64,
     max_memory: ?u64,
     export_symbol_names: []const []const u8,
src/main.zig
@@ -432,6 +432,8 @@ const usage_build_generic =
     \\  -F[dir]                        (Darwin) add search path for frameworks
     \\  -install_name=[value]          (Darwin) add dylib's install name
     \\  --import-memory                (WebAssembly) import memory from the environment
+    \\  --import-table                 (WebAssembly) import function table from the host environment
+    \\  --export-table                 (WebAssembly) export function table to the host environment
     \\  --initial-memory=[bytes]       (WebAssembly) initial size of the linear memory
     \\  --max-memory=[bytes]           (WebAssembly) maximum size of the linear memory
     \\  --global-base=[addr]           (WebAssembly) where to start to place global data
@@ -627,6 +629,8 @@ fn buildOutputType(
     var linker_allow_shlib_undefined: ?bool = null;
     var linker_bind_global_refs_locally: ?bool = null;
     var linker_import_memory: ?bool = null;
+    var linker_import_table: bool = false;
+    var linker_export_table: bool = false;
     var linker_initial_memory: ?u64 = null;
     var linker_max_memory: ?u64 = null;
     var linker_global_base: ?u64 = null;
@@ -1179,6 +1183,10 @@ fn buildOutputType(
                         }
                     } else if (mem.eql(u8, arg, "--import-memory")) {
                         linker_import_memory = true;
+                    } else if (mem.eql(u8, arg, "--import-table")) {
+                        linker_import_table = true;
+                    } else if (mem.eql(u8, arg, "--export-table")) {
+                        linker_export_table = true;
                     } else if (mem.startsWith(u8, arg, "--initial-memory=")) {
                         linker_initial_memory = parseIntSuffix(arg, "--initial-memory=".len);
                     } else if (mem.startsWith(u8, arg, "--max-memory=")) {
@@ -1560,6 +1568,10 @@ fn buildOutputType(
                     linker_bind_global_refs_locally = true;
                 } else if (mem.eql(u8, arg, "--import-memory")) {
                     linker_import_memory = true;
+                } else if (mem.eql(u8, arg, "--import-table")) {
+                    linker_import_table = true;
+                } else if (mem.eql(u8, arg, "--export-table")) {
+                    linker_export_table = true;
                 } else if (mem.startsWith(u8, arg, "--initial-memory=")) {
                     linker_initial_memory = parseIntSuffix(arg, "--initial-memory=".len);
                 } else if (mem.startsWith(u8, arg, "--max-memory=")) {
@@ -2455,6 +2467,8 @@ fn buildOutputType(
         .linker_allow_shlib_undefined = linker_allow_shlib_undefined,
         .linker_bind_global_refs_locally = linker_bind_global_refs_locally,
         .linker_import_memory = linker_import_memory,
+        .linker_import_table = linker_import_table,
+        .linker_export_table = linker_export_table,
         .linker_initial_memory = linker_initial_memory,
         .linker_max_memory = linker_max_memory,
         .linker_global_base = linker_global_base,