authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-02-28 19:32:10+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-03-01 08:35:20+01:00
logf5a31cb0d693ef3c07da7f446c95999cef34488b
tree8b4ea40e8a2d8240d0559ef9220ccd4c8fe9868b
parentb1159ab7aecfe704adcb4cf44d6fedafd572a720

wasm-linker: Intern globals, exports & imports

Symbols that have globals used to have their lookup key be the symbol name. This key is now the offset into the string table. Imports have both the module name (library name) and name (of the symbol), those strings are now also being interned. This can save us up to 24bytes per import which have both their module name and name de-duplicated. Module names are almost entirely the same for all imports, providing us with a big chance of saving us 12 bytes at least. Just like imports, exports can also have a seperate name than the internal symbol name. Rather than storing the slice, we now store the offset of this string instead.

3 files changed, 122 insertions(+), 73 deletions(-)

src/link/Wasm.zig+85-49
...@@ -69,8 +69,8 @@ imported_globals_count: u32 = 0,...@@ -69,8 +69,8 @@ imported_globals_count: u32 = 0,
69/// The count of imported tables. This number will be appended69/// The count of imported tables. This number will be appended
70/// to the table indexes when sections are merged.70/// to the table indexes when sections are merged.
71imported_tables_count: u32 = 0,71imported_tables_count: u32 = 0,
72/// Map of symbol locations, represented by its `wasm.Import`72/// Map of symbol locations, represented by its `types.Import`
73imports: std.AutoHashMapUnmanaged(SymbolLoc, wasm.Import) = .{},73imports: std.AutoHashMapUnmanaged(SymbolLoc, types.Import) = .{},
74/// Represents non-synthetic section entries.74/// Represents non-synthetic section entries.
75/// Used for code, data and custom sections.75/// Used for code, data and custom sections.
76segments: std.ArrayListUnmanaged(Segment) = .{},76segments: std.ArrayListUnmanaged(Segment) = .{},
...@@ -94,7 +94,7 @@ memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },...@@ -94,7 +94,7 @@ memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
94/// Output table section94/// Output table section
95tables: std.ArrayListUnmanaged(wasm.Table) = .{},95tables: std.ArrayListUnmanaged(wasm.Table) = .{},
96/// Output export section96/// Output export section
97exports: std.ArrayListUnmanaged(wasm.Export) = .{},97exports: std.ArrayListUnmanaged(types.Export) = .{},
9898
99/// Indirect function table, used to call function pointers99/// Indirect function table, used to call function pointers
100/// When this is non-zero, we must emit a table entry,100/// When this is non-zero, we must emit a table entry,
...@@ -105,8 +105,8 @@ function_table: std.AutoHashMapUnmanaged(u32, u32) = .{},...@@ -105,8 +105,8 @@ function_table: std.AutoHashMapUnmanaged(u32, u32) = .{},
105105
106/// All object files and their data which are linked into the final binary106/// All object files and their data which are linked into the final binary
107objects: std.ArrayListUnmanaged(Object) = .{},107objects: std.ArrayListUnmanaged(Object) = .{},
108/// A map of global names to their symbol location108/// A map of global names (read: offset into string table) to their symbol location
109globals: std.StringHashMapUnmanaged(SymbolLoc) = .{},109globals: std.AutoHashMapUnmanaged(u32, SymbolLoc) = .{},
110/// Maps discarded symbols and their positions to the location of the symbol110/// Maps discarded symbols and their positions to the location of the symbol
111/// it was resolved to111/// it was resolved to
112discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},112discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
...@@ -119,7 +119,8 @@ resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{},...@@ -119,7 +119,8 @@ resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{},
119symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, *Atom) = .{},119symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, *Atom) = .{},
120/// Maps a symbol's location to its export name, which may differ from the decl's name120/// Maps a symbol's location to its export name, which may differ from the decl's name
121/// which does the exporting.121/// which does the exporting.
122export_names: std.AutoHashMapUnmanaged(SymbolLoc, []const u8) = .{},122/// Note: The value represents the offset into the string table, rather than the actual string.
123export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{},
123124
124pub const Segment = struct {125pub const Segment = struct {
125 alignment: u32,126 alignment: u32,
...@@ -223,6 +224,15 @@ pub const StringTable = struct {...@@ -223,6 +224,15 @@ pub const StringTable = struct {
223 return mem.sliceTo(@ptrCast([*:0]const u8, self.string_data.items.ptr + off), 0);224 return mem.sliceTo(@ptrCast([*:0]const u8, self.string_data.items.ptr + off), 0);
224 }225 }
225226
227 /// Returns the offset of a given string when it exists.
228 /// Will return null if the given string does not yet exist within the string table.
229 pub fn getOffset(self: *StringTable, string: []const u8) ?u32 {
230 return self.string_table.getKeyAdapted(
231 string,
232 std.hash_map.StringIndexAdapter{ .bytes = &self.string_data },
233 );
234 }
235
226 /// Frees all resources of the string table. Any references pointing236 /// Frees all resources of the string table. Any references pointing
227 /// to the strings will be invalid.237 /// to the strings will be invalid.
228 pub fn deinit(self: *StringTable, allocator: Allocator) void {238 pub fn deinit(self: *StringTable, allocator: Allocator) void {
...@@ -250,16 +260,17 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -250,16 +260,17 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
250 try file.writeAll(&(wasm.magic ++ wasm.version));260 try file.writeAll(&(wasm.magic ++ wasm.version));
251261
252 // As sym_index '0' is reserved, we use it for our stack pointer symbol262 // As sym_index '0' is reserved, we use it for our stack pointer symbol
263 const sym_name = try wasm_bin.string_table.put(allocator, "__stack_pointer");
253 const symbol = try wasm_bin.symbols.addOne(allocator);264 const symbol = try wasm_bin.symbols.addOne(allocator);
254 symbol.* = .{265 symbol.* = .{
255 .name = try wasm_bin.string_table.put(allocator, "__stack_pointer"),266 .name = sym_name,
256 .tag = .global,267 .tag = .global,
257 .flags = 0,268 .flags = 0,
258 .index = 0,269 .index = 0,
259 };270 };
260 const loc: SymbolLoc = .{ .file = null, .index = 0 };271 const loc: SymbolLoc = .{ .file = null, .index = 0 };
261 try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});272 try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});
262 try wasm_bin.globals.putNoClobber(allocator, "__stack_pointer", loc);273 try wasm_bin.globals.putNoClobber(allocator, sym_name, loc);
263274
264 // For object files we will import the stack pointer symbol275 // For object files we will import the stack pointer symbol
265 if (options.output_mode == .Obj) {276 if (options.output_mode == .Obj) {
...@@ -268,8 +279,8 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -268,8 +279,8 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
268 allocator,279 allocator,
269 .{ .file = null, .index = 0 },280 .{ .file = null, .index = 0 },
270 .{281 .{
271 .module_name = wasm_bin.host_name,282 .module_name = try wasm_bin.string_table.put(allocator, wasm_bin.host_name),
272 .name = "__stack_pointer",283 .name = sym_name,
273 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },284 .kind = .{ .global = .{ .valtype = .i32, .mutable = true } },
274 },285 },
275 );286 );
...@@ -344,6 +355,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {...@@ -344,6 +355,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
344 .index = sym_index,355 .index = sym_index,
345 };356 };
346 const sym_name = object.string_table.get(symbol.name);357 const sym_name = object.string_table.get(symbol.name);
358 const sym_name_index = try self.string_table.put(self.base.allocator, sym_name);
347359
348 if (symbol.isLocal()) {360 if (symbol.isLocal()) {
349 if (symbol.isUndefined()) {361 if (symbol.isUndefined()) {
...@@ -358,7 +370,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {...@@ -358,7 +370,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
358 // TODO: locals are allowed to have duplicate symbol names370 // TODO: locals are allowed to have duplicate symbol names
359 // TODO: Store undefined symbols so we can verify at the end if they've all been found371 // TODO: Store undefined symbols so we can verify at the end if they've all been found
360 // if not, emit an error (unless --allow-undefined is enabled).372 // if not, emit an error (unless --allow-undefined is enabled).
361 const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name);373 const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name_index);
362 if (!maybe_existing.found_existing) {374 if (!maybe_existing.found_existing) {
363 maybe_existing.value_ptr.* = location;375 maybe_existing.value_ptr.* = location;
364 try self.resolved_symbols.putNoClobber(self.base.allocator, location, {});376 try self.resolved_symbols.putNoClobber(self.base.allocator, location, {});
...@@ -383,13 +395,18 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {...@@ -383,13 +395,18 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
383 continue; // Do not overwrite defined symbols with undefined symbols395 continue; // Do not overwrite defined symbols with undefined symbols
384 }396 }
385397
398 // when both symbols are weak, we skip overwriting
399 if (existing_sym.isWeak() and symbol.isWeak()) {
400 continue;
401 }
402
386 // simply overwrite with the new symbol403 // simply overwrite with the new symbol
387 log.debug("Overwriting symbol '{s}'", .{sym_name});404 log.debug("Overwriting symbol '{s}'", .{sym_name});
388 log.debug(" old definition in '{s}'", .{existing_file_path});405 log.debug(" old definition in '{s}'", .{existing_file_path});
389 log.debug(" new definition in '{s}'", .{object.name});406 log.debug(" new definition in '{s}'", .{object.name});
390 try self.discarded.putNoClobber(self.base.allocator, maybe_existing.value_ptr.*, location);407 try self.discarded.putNoClobber(self.base.allocator, maybe_existing.value_ptr.*, location);
391 maybe_existing.value_ptr.* = location;408 maybe_existing.value_ptr.* = location;
392 try self.globals.put(self.base.allocator, sym_name, location);409 try self.globals.put(self.base.allocator, sym_name_index, location);
393 try self.resolved_symbols.put(self.base.allocator, location, {});410 try self.resolved_symbols.put(self.base.allocator, location, {});
394 assert(self.resolved_symbols.swapRemove(existing_loc));411 assert(self.resolved_symbols.swapRemove(existing_loc));
395 }412 }
...@@ -696,7 +713,7 @@ pub fn deleteExport(self: *Wasm, exp: Export) void {...@@ -696,7 +713,7 @@ pub fn deleteExport(self: *Wasm, exp: Export) void {
696 if (self.export_names.fetchRemove(loc)) |kv| {713 if (self.export_names.fetchRemove(loc)) |kv| {
697 assert(self.globals.remove(kv.value));714 assert(self.globals.remove(kv.value));
698 } else {715 } else {
699 assert(self.globals.remove(symbol_name));716 assert(self.globals.remove(symbol.name));
700 }717 }
701}718}
702719
...@@ -723,7 +740,9 @@ pub fn updateDeclExports(...@@ -723,7 +740,9 @@ pub fn updateDeclExports(
723 ));740 ));
724 continue;741 continue;
725 }742 }
726 if (self.globals.getPtr(exp.options.name)) |existing_loc| {743
744 const export_name = try self.string_table.put(self.base.allocator, exp.options.name);
745 if (self.globals.getPtr(export_name)) |existing_loc| {
727 if (existing_loc.index == decl.link.wasm.sym_index) continue;746 if (existing_loc.index == decl.link.wasm.sym_index) continue;
728 const existing_sym: Symbol = existing_loc.getSymbol(self).*;747 const existing_sym: Symbol = existing_loc.getSymbol(self).*;
729748
...@@ -775,13 +794,13 @@ pub fn updateDeclExports(...@@ -775,13 +794,13 @@ pub fn updateDeclExports(
775 }794 }
776 // Ensure the symbol will be exported using the given name795 // Ensure the symbol will be exported using the given name
777 if (!mem.eql(u8, exp.options.name, sym_loc.getName(self))) {796 if (!mem.eql(u8, exp.options.name, sym_loc.getName(self))) {
778 try self.export_names.put(self.base.allocator, sym_loc, exp.options.name);797 try self.export_names.put(self.base.allocator, sym_loc, export_name);
779 }798 }
780799
781 symbol.setGlobal(true);800 symbol.setGlobal(true);
782 try self.globals.put(801 try self.globals.put(
783 self.base.allocator,802 self.base.allocator,
784 exp.options.name,803 export_name,
785 sym_loc,804 sym_loc,
786 );805 );
787806
...@@ -832,14 +851,14 @@ fn mapFunctionTable(self: *Wasm) void {...@@ -832,14 +851,14 @@ fn mapFunctionTable(self: *Wasm) void {
832851
833fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {852fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {
834 // For the import name itself, we use the decl's name, rather than the fully qualified name853 // For the import name itself, we use the decl's name, rather than the fully qualified name
835 const decl_name = mem.sliceTo(decl.name, 0);854 const decl_name_index = try self.string_table.put(self.base.allocator, mem.sliceTo(decl.name, 0));
836 const symbol_index = decl.link.wasm.sym_index;855 const symbol_index = decl.link.wasm.sym_index;
837 const symbol: *Symbol = &self.symbols.items[symbol_index];856 const symbol: *Symbol = &self.symbols.items[symbol_index];
838 symbol.setUndefined(true);857 symbol.setUndefined(true);
839 symbol.setGlobal(true);858 symbol.setGlobal(true);
840 try self.globals.putNoClobber(859 try self.globals.putNoClobber(
841 self.base.allocator,860 self.base.allocator,
842 decl_name,861 decl_name_index,
843 .{ .file = null, .index = symbol_index },862 .{ .file = null, .index = symbol_index },
844 );863 );
845 try self.resolved_symbols.put(self.base.allocator, .{ .file = null, .index = symbol_index }, {});864 try self.resolved_symbols.put(self.base.allocator, .{ .file = null, .index = symbol_index }, {});
...@@ -852,8 +871,8 @@ fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {...@@ -852,8 +871,8 @@ fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {
852 } else self.host_name;871 } else self.host_name;
853 if (!gop.found_existing) {872 if (!gop.found_existing) {
854 gop.value_ptr.* = .{873 gop.value_ptr.* = .{
855 .module_name = module_name,874 .module_name = try self.string_table.put(self.base.allocator, module_name),
856 .name = decl_name,875 .name = decl_name_index,
857 .kind = .{ .function = decl.fn_link.wasm.type_index },876 .kind = .{ .function = decl.fn_link.wasm.type_index },
858 };877 };
859 }878 }
...@@ -1001,9 +1020,18 @@ fn setupImports(self: *Wasm) !void {...@@ -1001,9 +1020,18 @@ fn setupImports(self: *Wasm) !void {
1001 }1020 }
10021021
1003 log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(self)});1022 log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(self)});
1004 const import = self.objects.items[symbol_loc.file.?].findImport(symbol.tag.externalType(), symbol.index);1023 const object = self.objects.items[symbol_loc.file.?];
1005 // TODO: De-duplicate imports1024 const import = object.findImport(symbol.tag.externalType(), symbol.index);
1006 try self.imports.putNoClobber(self.base.allocator, symbol_loc, import);1025
1026 // We copy the import to a new import to ensure the names contain references
1027 // to the internal string table, rather than of the object file.
1028 var new_imp: types.Import = .{
1029 .module_name = try self.string_table.put(self.base.allocator, object.string_table.get(import.module_name)),
1030 .name = try self.string_table.put(self.base.allocator, object.string_table.get(import.name)),
1031 .kind = import.kind,
1032 };
1033 // TODO: De-duplicate imports when they contain the same names and type
1034 try self.imports.putNoClobber(self.base.allocator, symbol_loc, new_imp);
1007 }1035 }
10081036
1009 // Assign all indexes of the imports to their representing symbols1037 // Assign all indexes of the imports to their representing symbols
...@@ -1013,7 +1041,7 @@ fn setupImports(self: *Wasm) !void {...@@ -1013,7 +1041,7 @@ fn setupImports(self: *Wasm) !void {
1013 var it = self.imports.iterator();1041 var it = self.imports.iterator();
1014 while (it.next()) |entry| {1042 while (it.next()) |entry| {
1015 const symbol = entry.key_ptr.*.getSymbol(self);1043 const symbol = entry.key_ptr.*.getSymbol(self);
1016 const import: wasm.Import = entry.value_ptr.*;1044 const import: types.Import = entry.value_ptr.*;
1017 switch (import.kind) {1045 switch (import.kind) {
1018 .function => {1046 .function => {
1019 symbol.index = function_index;1047 symbol.index = function_index;
...@@ -1045,7 +1073,8 @@ fn setupImports(self: *Wasm) !void {...@@ -1045,7 +1073,8 @@ fn setupImports(self: *Wasm) !void {
1045/// and merges it into a single section for each.1073/// and merges it into a single section for each.
1046fn mergeSections(self: *Wasm) !void {1074fn mergeSections(self: *Wasm) !void {
1047 // append the indirect function table if initialized1075 // append the indirect function table if initialized
1048 if (self.globals.get("__indirect_function_table")) |sym_loc| {1076 if (self.string_table.getOffset("__indirect_function_table")) |offset| {
1077 const sym_loc = self.globals.get(offset).?;
1049 const table: wasm.Table = .{1078 const table: wasm.Table = .{
1050 .limits = .{ .min = @intCast(u32, self.function_table.count()), .max = null },1079 .limits = .{ .min = @intCast(u32, self.function_table.count()), .max = null },
1051 .reftype = .funcref,1080 .reftype = .funcref,
...@@ -1114,7 +1143,7 @@ fn mergeTypes(self: *Wasm) !void {...@@ -1114,7 +1143,7 @@ fn mergeTypes(self: *Wasm) !void {
11141143
1115 if (symbol.isUndefined()) {1144 if (symbol.isUndefined()) {
1116 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)});1145 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)});
1117 const import: *wasm.Import = self.imports.getPtr(sym_loc).?;1146 const import: *types.Import = self.imports.getPtr(sym_loc).?;
1118 const original_type = object.func_types[import.kind.function];1147 const original_type = object.func_types[import.kind.function];
1119 import.kind.function = try self.putOrGetFuncType(original_type);1148 import.kind.function = try self.putOrGetFuncType(original_type);
1120 } else {1149 } else {
...@@ -1135,13 +1164,13 @@ fn setupExports(self: *Wasm) !void {...@@ -1135,13 +1164,13 @@ fn setupExports(self: *Wasm) !void {
1135 if (!symbol.isExported()) continue;1164 if (!symbol.isExported()) continue;
11361165
1137 const sym_name = sym_loc.getName(self);1166 const sym_name = sym_loc.getName(self);
1138 const export_name = if (self.export_names.get(sym_loc)) |name| name else sym_name;1167 const export_name = if (self.export_names.get(sym_loc)) |name| name else symbol.name;
1139 const exp: wasm.Export = .{1168 const exp: types.Export = .{
1140 .name = export_name,1169 .name = export_name,
1141 .kind = symbol.tag.externalType(),1170 .kind = symbol.tag.externalType(),
1142 .index = symbol.index,1171 .index = symbol.index,
1143 };1172 };
1144 log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ sym_name, exp.name, exp.index });1173 log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ sym_name, self.string_table.get(exp.name), exp.index });
1145 try self.exports.append(self.base.allocator, exp);1174 try self.exports.append(self.base.allocator, exp);
1146 }1175 }
11471176
...@@ -1151,7 +1180,7 @@ fn setupExports(self: *Wasm) !void {...@@ -1151,7 +1180,7 @@ fn setupExports(self: *Wasm) !void {
1151fn setupStart(self: *Wasm) !void {1180fn setupStart(self: *Wasm) !void {
1152 const entry_name = self.base.options.entry orelse "_start";1181 const entry_name = self.base.options.entry orelse "_start";
11531182
1154 const symbol_loc = self.globals.get(entry_name) orelse {1183 const symbol_name_offset = self.string_table.getOffset(entry_name) orelse {
1155 if (self.base.options.output_mode == .Exe) {1184 if (self.base.options.output_mode == .Exe) {
1156 if (self.base.options.wasi_exec_model == .reactor) return; // Not required for reactors1185 if (self.base.options.wasi_exec_model == .reactor) return; // Not required for reactors
1157 } else {1186 } else {
...@@ -1161,6 +1190,7 @@ fn setupStart(self: *Wasm) !void {...@@ -1161,6 +1190,7 @@ fn setupStart(self: *Wasm) !void {
1161 return error.MissingSymbol;1190 return error.MissingSymbol;
1162 };1191 };
11631192
1193 const symbol_loc = self.globals.get(symbol_name_offset).?;
1164 const symbol = symbol_loc.getSymbol(self);1194 const symbol = symbol_loc.getSymbol(self);
1165 if (symbol.tag != .function) {1195 if (symbol.tag != .function) {
1166 log.err("Entry symbol '{s}' is not a function", .{entry_name});1196 log.err("Entry symbol '{s}' is not a function", .{entry_name});
...@@ -1443,9 +1473,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -1443,9 +1473,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
14431473
1444 // import table is always first table so emit that first1474 // import table is always first table so emit that first
1445 if (import_table) {1475 if (import_table) {
1446 const table_imp: wasm.Import = .{1476 const table_imp: types.Import = .{
1447 .module_name = self.host_name,1477 .module_name = try self.string_table.put(self.base.allocator, self.host_name),
1448 .name = "__indirect_function_table",1478 .name = try self.string_table.put(self.base.allocator, "__indirect_function_table"),
1449 .kind = .{1479 .kind = .{
1450 .table = .{1480 .table = .{
1451 .limits = .{1481 .limits = .{
...@@ -1456,23 +1486,23 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -1456,23 +1486,23 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
1456 },1486 },
1457 },1487 },
1458 };1488 };
1459 try emitImport(writer, table_imp);1489 try self.emitImport(writer, table_imp);
1460 }1490 }
14611491
1462 var it = self.imports.iterator();1492 var it = self.imports.iterator();
1463 while (it.next()) |entry| {1493 while (it.next()) |entry| {
1464 assert(entry.key_ptr.*.getSymbol(self).isUndefined());1494 assert(entry.key_ptr.*.getSymbol(self).isUndefined());
1465 const import = entry.value_ptr.*;1495 const import = entry.value_ptr.*;
1466 try emitImport(writer, import);1496 try self.emitImport(writer, import);
1467 }1497 }
14681498
1469 if (import_memory) {1499 if (import_memory) {
1470 const mem_imp: wasm.Import = .{1500 const mem_imp: types.Import = .{
1471 .module_name = self.host_name,1501 .module_name = try self.string_table.put(self.base.allocator, self.host_name),
1472 .name = "__linear_memory",1502 .name = try self.string_table.put(self.base.allocator, "__linear_memory"),
1473 .kind = .{ .memory = self.memories.limits },1503 .kind = .{ .memory = self.memories.limits },
1474 };1504 };
1475 try emitImport(writer, mem_imp);1505 try self.emitImport(writer, mem_imp);
1476 }1506 }
14771507
1478 try writeVecSectionHeader(1508 try writeVecSectionHeader(
...@@ -1567,8 +1597,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -1567,8 +1597,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
1567 const header_offset = try reserveVecSectionHeader(file);1597 const header_offset = try reserveVecSectionHeader(file);
1568 const writer = file.writer();1598 const writer = file.writer();
1569 for (self.exports.items) |exp| {1599 for (self.exports.items) |exp| {
1570 try leb.writeULEB128(writer, @intCast(u32, exp.name.len));1600 const name = self.string_table.get(exp.name);
1571 try writer.writeAll(exp.name);1601 try leb.writeULEB128(writer, @intCast(u32, name.len));
1602 try writer.writeAll(name);
1572 try leb.writeULEB128(writer, @enumToInt(exp.kind));1603 try leb.writeULEB128(writer, @enumToInt(exp.kind));
1573 try leb.writeULEB128(writer, exp.index);1604 try leb.writeULEB128(writer, exp.index);
1574 }1605 }
...@@ -1747,9 +1778,12 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {...@@ -1747,9 +1778,12 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
17471778
1748 for (self.resolved_symbols.keys()) |sym_loc| {1779 for (self.resolved_symbols.keys()) |sym_loc| {
1749 const symbol = sym_loc.getSymbol(self).*;1780 const symbol = sym_loc.getSymbol(self).*;
1781 const name = if (symbol.isUndefined()) blk: {
1782 break :blk self.string_table.get(self.imports.get(sym_loc).?.name);
1783 } else sym_loc.getName(self);
1750 switch (symbol.tag) {1784 switch (symbol.tag) {
1751 .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = sym_loc.getName(self) }),1785 .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
1752 .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = sym_loc.getName(self) }),1786 .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
1753 else => {},1787 else => {},
1754 }1788 }
1755 }1789 }
...@@ -1831,12 +1865,14 @@ fn emitInit(writer: anytype, init_expr: wasm.InitExpression) !void {...@@ -1831,12 +1865,14 @@ fn emitInit(writer: anytype, init_expr: wasm.InitExpression) !void {
1831 try writer.writeByte(wasm.opcode(.end));1865 try writer.writeByte(wasm.opcode(.end));
1832}1866}
18331867
1834fn emitImport(writer: anytype, import: wasm.Import) !void {1868fn emitImport(self: *Wasm, writer: anytype, import: types.Import) !void {
1835 try leb.writeULEB128(writer, @intCast(u32, import.module_name.len));1869 const module_name = self.string_table.get(import.module_name);
1836 try writer.writeAll(import.module_name);1870 try leb.writeULEB128(writer, @intCast(u32, module_name.len));
1871 try writer.writeAll(module_name);
18371872
1838 try leb.writeULEB128(writer, @intCast(u32, import.name.len));1873 const name = self.string_table.get(import.name);
1839 try writer.writeAll(import.name);1874 try leb.writeULEB128(writer, @intCast(u32, name.len));
1875 try writer.writeAll(name);
18401876
1841 try writer.writeByte(@enumToInt(import.kind));1877 try writer.writeByte(@enumToInt(import.kind));
1842 switch (import.kind) {1878 switch (import.kind) {
...@@ -2353,7 +2389,7 @@ fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *...@@ -2353,7 +2389,7 @@ fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *
2353 try leb.writeULEB128(writer, @enumToInt(symbol.tag));2389 try leb.writeULEB128(writer, @enumToInt(symbol.tag));
2354 try leb.writeULEB128(writer, symbol.flags);2390 try leb.writeULEB128(writer, symbol.flags);
23552391
2356 const sym_name = if (self.export_names.get(sym_loc)) |exp_name| exp_name else sym_loc.getName(self);2392 const sym_name = if (self.export_names.get(sym_loc)) |exp_name| self.string_table.get(exp_name) else sym_loc.getName(self);
2357 switch (symbol.tag) {2393 switch (symbol.tag) {
2358 .data => {2394 .data => {
2359 try leb.writeULEB128(writer, @intCast(u32, sym_name.len));2395 try leb.writeULEB128(writer, @intCast(u32, sym_name.len));
src/link/Wasm/Object.zig+17-24
...@@ -24,7 +24,7 @@ name: []const u8,...@@ -24,7 +24,7 @@ name: []const u8,
24/// Parsed type section24/// Parsed type section
25func_types: []const std.wasm.Type = &.{},25func_types: []const std.wasm.Type = &.{},
26/// A list of all imports for this module26/// A list of all imports for this module
27imports: []const std.wasm.Import = &.{},27imports: []const types.Import = &.{},
28/// Parsed function section28/// Parsed function section
29functions: []const std.wasm.Func = &.{},29functions: []const std.wasm.Func = &.{},
30/// Parsed table section30/// Parsed table section
...@@ -34,7 +34,7 @@ memories: []const std.wasm.Memory = &.{},...@@ -34,7 +34,7 @@ memories: []const std.wasm.Memory = &.{},
34/// Parsed global section34/// Parsed global section
35globals: []const std.wasm.Global = &.{},35globals: []const std.wasm.Global = &.{},
36/// Parsed export section36/// Parsed export section
37exports: []const std.wasm.Export = &.{},37exports: []const types.Export = &.{},
38/// Parsed element section38/// Parsed element section
39elements: []const std.wasm.Element = &.{},39elements: []const std.wasm.Element = &.{},
40/// Represents the function ID that must be called on startup.40/// Represents the function ID that must be called on startup.
...@@ -127,18 +127,11 @@ pub fn deinit(self: *Object, gpa: Allocator) void {...@@ -127,18 +127,11 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
127 gpa.free(func_ty.returns);127 gpa.free(func_ty.returns);
128 }128 }
129 gpa.free(self.func_types);129 gpa.free(self.func_types);
130 for (self.imports) |imp| {
131 gpa.free(imp.name);
132 gpa.free(imp.module_name);
133 }
134 gpa.free(self.functions);130 gpa.free(self.functions);
135 gpa.free(self.imports);131 gpa.free(self.imports);
136 gpa.free(self.tables);132 gpa.free(self.tables);
137 gpa.free(self.memories);133 gpa.free(self.memories);
138 gpa.free(self.globals);134 gpa.free(self.globals);
139 for (self.exports) |exp| {
140 gpa.free(exp.name);
141 }
142 gpa.free(self.exports);135 gpa.free(self.exports);
143 gpa.free(self.elements);136 gpa.free(self.elements);
144 gpa.free(self.features);137 gpa.free(self.features);
...@@ -163,7 +156,7 @@ pub fn deinit(self: *Object, gpa: Allocator) void {...@@ -163,7 +156,7 @@ pub fn deinit(self: *Object, gpa: Allocator) void {
163156
164/// Finds the import within the list of imports from a given kind and index of that kind.157/// Finds the import within the list of imports from a given kind and index of that kind.
165/// Asserts the import exists158/// Asserts the import exists
166pub fn findImport(self: *const Object, import_kind: std.wasm.ExternalKind, index: u32) std.wasm.Import {159pub fn findImport(self: *const Object, import_kind: std.wasm.ExternalKind, index: u32) types.Import {
167 var i: u32 = 0;160 var i: u32 = 0;
168 return for (self.imports) |import| {161 return for (self.imports) |import| {
169 if (std.meta.activeTag(import.kind) == import_kind) {162 if (std.meta.activeTag(import.kind) == import_kind) {
...@@ -187,7 +180,7 @@ pub fn importedCountByKind(self: *const Object, kind: std.wasm.ExternalKind) u32...@@ -187,7 +180,7 @@ pub fn importedCountByKind(self: *const Object, kind: std.wasm.ExternalKind) u32
187/// we initialize a new table symbol that corresponds to that import and return that symbol.180/// we initialize a new table symbol that corresponds to that import and return that symbol.
188///181///
189/// When the object file is *NOT* MVP, we return `null`.182/// When the object file is *NOT* MVP, we return `null`.
190fn checkLegacyIndirectFunctionTable(self: *Object, gpa: Allocator) !?Symbol {183fn checkLegacyIndirectFunctionTable(self: *Object) !?Symbol {
191 var table_count: usize = 0;184 var table_count: usize = 0;
192 for (self.symtable) |sym| {185 for (self.symtable) |sym| {
193 if (sym.tag == .table) table_count += 1;186 if (sym.tag == .table) table_count += 1;
...@@ -217,20 +210,20 @@ fn checkLegacyIndirectFunctionTable(self: *Object, gpa: Allocator) !?Symbol {...@@ -217,20 +210,20 @@ fn checkLegacyIndirectFunctionTable(self: *Object, gpa: Allocator) !?Symbol {
217 return error.MissingTableSymbols;210 return error.MissingTableSymbols;
218 }211 }
219212
220 var table_import: std.wasm.Import = for (self.imports) |imp| {213 var table_import: types.Import = for (self.imports) |imp| {
221 if (imp.kind == .table) {214 if (imp.kind == .table) {
222 break imp;215 break imp;
223 }216 }
224 } else unreachable;217 } else unreachable;
225218
226 if (!std.mem.eql(u8, table_import.name, "__indirect_function_table")) {219 if (!std.mem.eql(u8, self.string_table.get(table_import.name), "__indirect_function_table")) {
227 log.err("Non-indirect function table import '{s}' is missing a corresponding symbol", .{table_import.name});220 log.err("Non-indirect function table import '{s}' is missing a corresponding symbol", .{self.string_table.get(table_import.name)});
228 return error.MissingTableSymbols;221 return error.MissingTableSymbols;
229 }222 }
230223
231 var table_symbol: Symbol = .{224 var table_symbol: Symbol = .{
232 .flags = 0,225 .flags = 0,
233 .name = try self.string_table.put(gpa, table_import.name),226 .name = table_import.name,
234 .tag = .table,227 .tag = .table,
235 .index = 0,228 .index = 0,
236 };229 };
...@@ -353,12 +346,12 @@ fn Parser(comptime ReaderType: type) type {...@@ -353,12 +346,12 @@ fn Parser(comptime ReaderType: type) type {
353 for (try readVec(&self.object.imports, reader, gpa)) |*import| {346 for (try readVec(&self.object.imports, reader, gpa)) |*import| {
354 const module_len = try readLeb(u32, reader);347 const module_len = try readLeb(u32, reader);
355 const module_name = try gpa.alloc(u8, module_len);348 const module_name = try gpa.alloc(u8, module_len);
356 errdefer gpa.free(module_name);349 defer gpa.free(module_name);
357 try reader.readNoEof(module_name);350 try reader.readNoEof(module_name);
358351
359 const name_len = try readLeb(u32, reader);352 const name_len = try readLeb(u32, reader);
360 const name = try gpa.alloc(u8, name_len);353 const name = try gpa.alloc(u8, name_len);
361 errdefer gpa.free(name);354 defer gpa.free(name);
362 try reader.readNoEof(name);355 try reader.readNoEof(name);
363356
364 const kind = try readEnum(std.wasm.ExternalKind, reader);357 const kind = try readEnum(std.wasm.ExternalKind, reader);
...@@ -376,8 +369,8 @@ fn Parser(comptime ReaderType: type) type {...@@ -376,8 +369,8 @@ fn Parser(comptime ReaderType: type) type {
376 };369 };
377370
378 import.* = .{371 import.* = .{
379 .module_name = module_name,372 .module_name = try self.object.string_table.put(gpa, module_name),
380 .name = name,373 .name = try self.object.string_table.put(gpa, name),
381 .kind = kind_value,374 .kind = kind_value,
382 };375 };
383 }376 }
...@@ -420,10 +413,10 @@ fn Parser(comptime ReaderType: type) type {...@@ -420,10 +413,10 @@ fn Parser(comptime ReaderType: type) type {
420 for (try readVec(&self.object.exports, reader, gpa)) |*exp| {413 for (try readVec(&self.object.exports, reader, gpa)) |*exp| {
421 const name_len = try readLeb(u32, reader);414 const name_len = try readLeb(u32, reader);
422 const name = try gpa.alloc(u8, name_len);415 const name = try gpa.alloc(u8, name_len);
423 errdefer gpa.free(name);416 defer gpa.free(name);
424 try reader.readNoEof(name);417 try reader.readNoEof(name);
425 exp.* = .{418 exp.* = .{
426 .name = name,419 .name = try self.object.string_table.put(gpa, name),
427 .kind = try readEnum(std.wasm.ExternalKind, reader),420 .kind = try readEnum(std.wasm.ExternalKind, reader),
428 .index = try readLeb(u32, reader),421 .index = try readLeb(u32, reader),
429 };422 };
...@@ -675,7 +668,7 @@ fn Parser(comptime ReaderType: type) type {...@@ -675,7 +668,7 @@ fn Parser(comptime ReaderType: type) type {
675668
676 // we found all symbols, check for indirect function table669 // we found all symbols, check for indirect function table
677 // in case of an MVP object file670 // in case of an MVP object file
678 if (try self.object.checkLegacyIndirectFunctionTable(gpa)) |symbol| {671 if (try self.object.checkLegacyIndirectFunctionTable()) |symbol| {
679 try symbols.append(symbol);672 try symbols.append(symbol);
680 log.debug("Found legacy indirect function table. Created symbol", .{});673 log.debug("Found legacy indirect function table. Created symbol", .{});
681 }674 }
...@@ -720,7 +713,7 @@ fn Parser(comptime ReaderType: type) type {...@@ -720,7 +713,7 @@ fn Parser(comptime ReaderType: type) type {
720 },713 },
721 else => {714 else => {
722 symbol.index = try leb.readULEB128(u32, reader);715 symbol.index = try leb.readULEB128(u32, reader);
723 var maybe_import: ?std.wasm.Import = null;716 var maybe_import: ?types.Import = null;
724717
725 const is_undefined = symbol.isUndefined();718 const is_undefined = symbol.isUndefined();
726 if (is_undefined) {719 if (is_undefined) {
...@@ -734,7 +727,7 @@ fn Parser(comptime ReaderType: type) type {...@@ -734,7 +727,7 @@ fn Parser(comptime ReaderType: type) type {
734 try reader.readNoEof(name);727 try reader.readNoEof(name);
735 symbol.name = try self.object.string_table.put(gpa, name);728 symbol.name = try self.object.string_table.put(gpa, name);
736 } else {729 } else {
737 symbol.name = try self.object.string_table.put(gpa, maybe_import.?.name);730 symbol.name = maybe_import.?.name;
738 }731 }
739 },732 },
740 }733 }
src/link/Wasm/types.zig+20
...@@ -78,6 +78,26 @@ pub const Relocation = struct {...@@ -78,6 +78,26 @@ pub const Relocation = struct {
78 }78 }
79};79};
8080
81/// Unlike the `Import` object defined by the wasm spec, and existing
82/// in the std.wasm namespace, this construct saves the 'module name' and 'name'
83/// of the import using offsets into a string table, rather than the slices itself.
84/// This saves us (potentially) 24 bytes per import on 64bit machines.
85pub const Import = struct {
86 module_name: u32,
87 name: u32,
88 kind: std.wasm.Import.Kind,
89};
90
91/// Unlike the `Export` object defined by the wasm spec, and existing
92/// in the std.wasm namespace, this construct saves the 'name'
93/// of the export using offsets into a string table, rather than the slice itself.
94/// This saves us (potentially) 12 bytes per export on 64bit machines.
95pub const Export = struct {
96 name: u32,
97 index: u32,
98 kind: std.wasm.ExternalKind,
99};
100
81pub const SubsectionType = enum(u8) {101pub const SubsectionType = enum(u8) {
82 WASM_SEGMENT_INFO = 5,102 WASM_SEGMENT_INFO = 5,
83 WASM_INIT_FUNCS = 6,103 WASM_INIT_FUNCS = 6,