| ... | ... | @@ -79,6 +79,8 @@ data_segments: std.StringArrayHashMapUnmanaged(u32) = .{}, |
| 79 | 79 | /// A list of `types.Segment` which provide meta data |
| 80 | 80 | /// about a data symbol such as its name |
| 81 | 81 | segment_info: std.ArrayListUnmanaged(types.Segment) = .{}, |
| 82 | /// Deduplicated string table for strings used by symbols, imports and exports. |
| 83 | string_table: StringTable = .{}, |
| 82 | 84 | |
| 83 | 85 | // Output sections |
| 84 | 86 | /// Output type section |
| ... | ... | @@ -155,6 +157,79 @@ pub const SymbolLoc = struct { |
| 155 | 157 | } |
| 156 | 158 | return &wasm_bin.symbols.items[self.index]; |
| 157 | 159 | } |
| 160 | |
| 161 | /// From a given location, returns the name of the symbol. |
| 162 | pub fn getName(self: SymbolLoc, wasm_bin: *const Wasm) []const u8 { |
| 163 | if (wasm_bin.discarded.get(self)) |new_loc| { |
| 164 | return new_loc.getName(wasm_bin); |
| 165 | } |
| 166 | if (self.file) |object_index| { |
| 167 | const object = wasm_bin.objects.items[object_index]; |
| 168 | return object.string_table.get(object.symtable[self.index].name); |
| 169 | } |
| 170 | return wasm_bin.string_table.get(wasm_bin.symbols.items[self.index].name); |
| 171 | } |
| 172 | }; |
| 173 | |
| 174 | /// Generic string table that duplicates strings |
| 175 | /// and converts them into offsets instead. |
| 176 | pub const StringTable = struct { |
| 177 | /// Table that maps string offsets, which is used to de-duplicate strings. |
| 178 | /// Rather than having the offset map to the data, the `StringContext` holds all bytes of the string. |
| 179 | /// The strings are stored as a contigious array where each string is zero-terminated. |
| 180 | string_table: std.HashMapUnmanaged( |
| 181 | u32, |
| 182 | void, |
| 183 | std.hash_map.StringIndexContext, |
| 184 | std.hash_map.default_max_load_percentage, |
| 185 | ) = .{}, |
| 186 | /// Holds the actual data of the string table. |
| 187 | string_data: std.ArrayListUnmanaged(u8) = .{}, |
| 188 | |
| 189 | /// Accepts a string and searches for a corresponding string. |
| 190 | /// When found, de-duplicates the string and returns the existing offset instead. |
| 191 | /// When the string is not found in the `string_table`, a new entry will be inserted |
| 192 | /// and the new offset to its data will be returned. |
| 193 | pub fn put(self: *StringTable, allocator: Allocator, string: []const u8) !u32 { |
| 194 | const gop = try self.string_table.getOrPutContextAdapted( |
| 195 | allocator, |
| 196 | string, |
| 197 | std.hash_map.StringIndexAdapter{ .bytes = &self.string_data }, |
| 198 | .{ .bytes = &self.string_data }, |
| 199 | ); |
| 200 | if (gop.found_existing) { |
| 201 | const off = gop.key_ptr.*; |
| 202 | log.debug("reusing string '{s}' at offset 0x{x}", .{ string, off }); |
| 203 | return off; |
| 204 | } |
| 205 | |
| 206 | try self.string_data.ensureUnusedCapacity(allocator, string.len + 1); |
| 207 | const offset = @intCast(u32, self.string_data.items.len); |
| 208 | |
| 209 | log.debug("writing new string '{s}' at offset 0x{x}", .{ string, offset }); |
| 210 | |
| 211 | self.string_data.appendSliceAssumeCapacity(string); |
| 212 | self.string_data.appendAssumeCapacity(0); |
| 213 | |
| 214 | gop.key_ptr.* = offset; |
| 215 | |
| 216 | return offset; |
| 217 | } |
| 218 | |
| 219 | /// From a given offset, returns its corresponding string value. |
| 220 | /// Asserts offset does not exceed bounds. |
| 221 | pub fn get(self: StringTable, off: u32) []const u8 { |
| 222 | assert(off < self.string_data.items.len); |
| 223 | return mem.sliceTo(@ptrCast([*:0]const u8, self.string_data.items.ptr + off), 0); |
| 224 | } |
| 225 | |
| 226 | /// Frees all resources of the string table. Any references pointing |
| 227 | /// to the strings will be invalid. |
| 228 | pub fn deinit(self: *StringTable, allocator: Allocator) void { |
| 229 | self.string_data.deinit(allocator); |
| 230 | self.string_table.deinit(allocator); |
| 231 | self.* = undefined; |
| 232 | } |
| 158 | 233 | }; |
| 159 | 234 | |
| 160 | 235 | pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm { |
| ... | ... | @@ -177,7 +252,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option |
| 177 | 252 | // As sym_index '0' is reserved, we use it for our stack pointer symbol |
| 178 | 253 | const symbol = try wasm_bin.symbols.addOne(allocator); |
| 179 | 254 | symbol.* = .{ |
| 180 | | .name = "__stack_pointer", |
| 255 | .name = try wasm_bin.string_table.put(allocator, "__stack_pointer"), |
| 181 | 256 | .tag = .global, |
| 182 | 257 | .flags = 0, |
| 183 | 258 | .index = 0, |
| ... | ... | @@ -268,12 +343,12 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { |
| 268 | 343 | .file = object_index, |
| 269 | 344 | .index = sym_index, |
| 270 | 345 | }; |
| 271 | | const sym_name = std.mem.sliceTo(symbol.name, 0); |
| 346 | const sym_name = object.string_table.get(symbol.name); |
| 272 | 347 | |
| 273 | 348 | if (symbol.isLocal()) { |
| 274 | 349 | if (symbol.isUndefined()) { |
| 275 | 350 | log.err("Local symbols are not allowed to reference imports", .{}); |
| 276 | | log.err(" symbol '{s}' defined in '{s}'", .{ symbol.name, object.name }); |
| 351 | log.err(" symbol '{s}' defined in '{s}'", .{ sym_name, object.name }); |
| 277 | 352 | return error.undefinedLocal; |
| 278 | 353 | } |
| 279 | 354 | try self.resolved_symbols.putNoClobber(self.base.allocator, location, {}); |
| ... | ... | @@ -299,7 +374,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { |
| 299 | 374 | |
| 300 | 375 | if (!existing_sym.isUndefined()) { |
| 301 | 376 | if (!symbol.isUndefined()) { |
| 302 | | log.err("symbol '{s}' defined multiple times", .{existing_sym.name}); |
| 377 | log.err("symbol '{s}' defined multiple times", .{sym_name}); |
| 303 | 378 | log.err(" first definition in '{s}'", .{existing_file_path}); |
| 304 | 379 | log.err(" next definition in '{s}'", .{object.name}); |
| 305 | 380 | return error.SymbolCollision; |
| ... | ... | @@ -309,7 +384,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void { |
| 309 | 384 | } |
| 310 | 385 | |
| 311 | 386 | // simply overwrite with the new symbol |
| 312 | | log.debug("Overwriting symbol '{s}'", .{symbol.name}); |
| 387 | log.debug("Overwriting symbol '{s}'", .{sym_name}); |
| 313 | 388 | log.debug(" old definition in '{s}'", .{existing_file_path}); |
| 314 | 389 | log.debug(" new definition in '{s}'", .{object.name}); |
| 315 | 390 | try self.discarded.putNoClobber(self.base.allocator, maybe_existing.value_ptr.*, location); |
| ... | ... | @@ -328,12 +403,7 @@ pub fn deinit(self: *Wasm) void { |
| 328 | 403 | |
| 329 | 404 | var decl_it = self.decls.keyIterator(); |
| 330 | 405 | while (decl_it.next()) |decl_ptr| { |
| 331 | | const decl = decl_ptr.*; |
| 332 | | const atom: *Atom = &decl.link.wasm; |
| 333 | | for (atom.locals.items) |local| { |
| 334 | | gpa.free(mem.sliceTo(self.symbols.items[local.sym_index].name, 0)); |
| 335 | | } |
| 336 | | decl.link.wasm.deinit(gpa); |
| 406 | decl_ptr.*.link.wasm.deinit(gpa); |
| 337 | 407 | } |
| 338 | 408 | |
| 339 | 409 | for (self.func_types.items) |*func_type| { |
| ... | ... | @@ -374,6 +444,8 @@ pub fn deinit(self: *Wasm) void { |
| 374 | 444 | self.function_table.deinit(gpa); |
| 375 | 445 | self.tables.deinit(gpa); |
| 376 | 446 | self.exports.deinit(gpa); |
| 447 | |
| 448 | self.string_table.deinit(gpa); |
| 377 | 449 | } |
| 378 | 450 | |
| 379 | 451 | pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void { |
| ... | ... | @@ -498,7 +570,10 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void { |
| 498 | 570 | atom.size = @intCast(u32, code.len); |
| 499 | 571 | atom.alignment = decl.ty.abiAlignment(self.base.options.target); |
| 500 | 572 | const symbol = &self.symbols.items[atom.sym_index]; |
| 501 | | symbol.name = decl.name; |
| 573 | |
| 574 | const full_name = try decl.getFullyQualifiedName(self.base.allocator); |
| 575 | defer self.base.allocator.free(full_name); |
| 576 | symbol.name = try self.string_table.put(self.base.allocator, full_name); |
| 502 | 577 | try atom.code.appendSlice(self.base.allocator, code); |
| 503 | 578 | } |
| 504 | 579 | |
| ... | ... | @@ -511,8 +586,9 @@ pub fn lowerUnnamedConst(self: *Wasm, decl: *Module.Decl, tv: TypedValue) !u32 { |
| 511 | 586 | // Create and initialize a new local symbol and atom |
| 512 | 587 | const local_index = decl.link.wasm.locals.items.len; |
| 513 | 588 | const name = try std.fmt.allocPrintZ(self.base.allocator, "__unnamed_{s}_{d}", .{ decl.name, local_index }); |
| 589 | defer self.base.allocator.free(name); |
| 514 | 590 | var symbol: Symbol = .{ |
| 515 | | .name = name, |
| 591 | .name = try self.string_table.put(self.base.allocator, name), |
| 516 | 592 | .flags = 0, |
| 517 | 593 | .tag = .data, |
| 518 | 594 | .index = undefined, |
| ... | ... | @@ -615,7 +691,7 @@ pub fn deleteExport(self: *Wasm, exp: Export) void { |
| 615 | 691 | const sym_index = exp.sym_index orelse return; |
| 616 | 692 | const loc: SymbolLoc = .{ .file = null, .index = sym_index }; |
| 617 | 693 | const symbol = loc.getSymbol(self); |
| 618 | | const symbol_name = mem.sliceTo(symbol.name, 0); |
| 694 | const symbol_name = self.string_table.get(symbol.name); |
| 619 | 695 | log.debug("Deleting export for decl '{s}'", .{symbol_name}); |
| 620 | 696 | if (self.export_names.fetchRemove(loc)) |kv| { |
| 621 | 697 | assert(self.globals.remove(kv.value)); |
| ... | ... | @@ -656,7 +732,7 @@ pub fn updateDeclExports( |
| 656 | 732 | // are strong symbols, we have a linker error. |
| 657 | 733 | // In the other case we replace one with the other. |
| 658 | 734 | if (!exp_is_weak and !existing_sym.isWeak()) { |
| 659 | | try module.failed_exports.putNoClobber(module.gpa, exp, try Module.ErrorMsg.create( |
| 735 | try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create( |
| 660 | 736 | module.gpa, |
| 661 | 737 | decl.srcLoc(), |
| 662 | 738 | \\LinkError: symbol '{s}' defined multiple times |
| ... | ... | @@ -665,6 +741,7 @@ pub fn updateDeclExports( |
| 665 | 741 | , |
| 666 | 742 | .{ exp.options.name, self.name, self.name }, |
| 667 | 743 | )); |
| 744 | continue; |
| 668 | 745 | } else if (exp_is_weak) { |
| 669 | 746 | continue; // to-be-exported symbol is weak, so we keep the existing symbol |
| 670 | 747 | } else { |
| ... | ... | @@ -697,7 +774,7 @@ pub fn updateDeclExports( |
| 697 | 774 | }, |
| 698 | 775 | } |
| 699 | 776 | // Ensure the symbol will be exported using the given name |
| 700 | | if (!mem.eql(u8, exp.options.name, mem.sliceTo(exp.exported_decl.name, 0))) { |
| 777 | if (!mem.eql(u8, exp.options.name, sym_loc.getName(self))) { |
| 701 | 778 | try self.export_names.put(self.base.allocator, sym_loc, exp.options.name); |
| 702 | 779 | } |
| 703 | 780 | |
| ... | ... | @@ -725,7 +802,6 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { |
| 725 | 802 | for (atom.locals.items) |local_atom| { |
| 726 | 803 | const local_symbol = &self.symbols.items[local_atom.sym_index]; |
| 727 | 804 | local_symbol.tag = .dead; // also for any local symbol |
| 728 | | self.base.allocator.free(mem.sliceTo(local_symbol.name, 0)); |
| 729 | 805 | self.symbols_free_list.append(self.base.allocator, local_atom.sym_index) catch {}; |
| 730 | 806 | assert(self.resolved_symbols.swapRemove(local_atom.symbolLoc())); |
| 731 | 807 | } |
| ... | ... | @@ -755,14 +831,15 @@ fn mapFunctionTable(self: *Wasm) void { |
| 755 | 831 | } |
| 756 | 832 | |
| 757 | 833 | fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void { |
| 834 | // 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); |
| 758 | 836 | const symbol_index = decl.link.wasm.sym_index; |
| 759 | 837 | const symbol: *Symbol = &self.symbols.items[symbol_index]; |
| 760 | | symbol.name = decl.name; |
| 761 | 838 | symbol.setUndefined(true); |
| 762 | 839 | symbol.setGlobal(true); |
| 763 | 840 | try self.globals.putNoClobber( |
| 764 | 841 | self.base.allocator, |
| 765 | | mem.sliceTo(symbol.name, 0), |
| 842 | decl_name, |
| 766 | 843 | .{ .file = null, .index = symbol_index }, |
| 767 | 844 | ); |
| 768 | 845 | try self.resolved_symbols.put(self.base.allocator, .{ .file = null, .index = symbol_index }, {}); |
| ... | ... | @@ -776,7 +853,7 @@ fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void { |
| 776 | 853 | if (!gop.found_existing) { |
| 777 | 854 | gop.value_ptr.* = .{ |
| 778 | 855 | .module_name = module_name, |
| 779 | | .name = mem.sliceTo(symbol.name, 0), |
| 856 | .name = decl_name, |
| 780 | 857 | .kind = .{ .function = decl.fn_link.wasm.type_index }, |
| 781 | 858 | }; |
| 782 | 859 | } |
| ... | ... | @@ -815,7 +892,7 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void { |
| 815 | 892 | // TODO: Add mutables global decls to .bss section instead |
| 816 | 893 | const segment_name = try std.mem.concat(self.base.allocator, u8, &.{ |
| 817 | 894 | ".rodata.", |
| 818 | | std.mem.span(symbol.name), |
| 895 | self.string_table.get(symbol.name), |
| 819 | 896 | }); |
| 820 | 897 | errdefer self.base.allocator.free(segment_name); |
| 821 | 898 | const segment_info: types.Segment = .{ |
| ... | ... | @@ -886,7 +963,7 @@ fn allocateAtoms(self: *Wasm) !void { |
| 886 | 963 | atom.offset = offset; |
| 887 | 964 | const symbol_loc = atom.symbolLoc(); |
| 888 | 965 | log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{ |
| 889 | | symbol_loc.getSymbol(self).name, |
| 966 | symbol_loc.getName(self), |
| 890 | 967 | offset, |
| 891 | 968 | offset + atom.size, |
| 892 | 969 | atom.size, |
| ... | ... | @@ -906,7 +983,7 @@ fn setupImports(self: *Wasm) !void { |
| 906 | 983 | // remove an import if it was resolved |
| 907 | 984 | if (self.imports.remove(discarded.*)) { |
| 908 | 985 | log.debug("Removed symbol '{s}' as an import", .{ |
| 909 | | discarded.getSymbol(self).name, |
| 986 | discarded.getName(self), |
| 910 | 987 | }); |
| 911 | 988 | } |
| 912 | 989 | } |
| ... | ... | @@ -923,7 +1000,7 @@ fn setupImports(self: *Wasm) !void { |
| 923 | 1000 | continue; |
| 924 | 1001 | } |
| 925 | 1002 | |
| 926 | | log.debug("Symbol '{s}' will be imported from the host", .{symbol.name}); |
| 1003 | log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(self)}); |
| 927 | 1004 | const import = self.objects.items[symbol_loc.file.?].findImport(symbol.tag.externalType(), symbol.index); |
| 928 | 1005 | // TODO: De-duplicate imports |
| 929 | 1006 | try self.imports.putNoClobber(self.base.allocator, symbol_loc, import); |
| ... | ... | @@ -1036,12 +1113,12 @@ fn mergeTypes(self: *Wasm) !void { |
| 1036 | 1113 | } |
| 1037 | 1114 | |
| 1038 | 1115 | if (symbol.isUndefined()) { |
| 1039 | | log.debug("Adding type from extern function '{s}'", .{symbol.name}); |
| 1116 | log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)}); |
| 1040 | 1117 | const import: *wasm.Import = self.imports.getPtr(sym_loc).?; |
| 1041 | 1118 | const original_type = object.func_types[import.kind.function]; |
| 1042 | 1119 | import.kind.function = try self.putOrGetFuncType(original_type); |
| 1043 | 1120 | } else { |
| 1044 | | log.debug("Adding type from function '{s}'", .{symbol.name}); |
| 1121 | log.debug("Adding type from function '{s}'", .{sym_loc.getName(self)}); |
| 1045 | 1122 | const func = &self.functions.items[symbol.index - self.imported_functions_count]; |
| 1046 | 1123 | func.type_index = try self.putOrGetFuncType(object.func_types[func.type_index]); |
| 1047 | 1124 | } |
| ... | ... | @@ -1057,13 +1134,14 @@ fn setupExports(self: *Wasm) !void { |
| 1057 | 1134 | const symbol = sym_loc.getSymbol(self); |
| 1058 | 1135 | if (!symbol.isExported()) continue; |
| 1059 | 1136 | |
| 1060 | | const export_name = if (self.export_names.get(sym_loc)) |name| name else mem.sliceTo(symbol.name, 0); |
| 1137 | const sym_name = sym_loc.getName(self); |
| 1138 | const export_name = if (self.export_names.get(sym_loc)) |name| name else sym_name; |
| 1061 | 1139 | const exp: wasm.Export = .{ |
| 1062 | 1140 | .name = export_name, |
| 1063 | 1141 | .kind = symbol.tag.externalType(), |
| 1064 | 1142 | .index = symbol.index, |
| 1065 | 1143 | }; |
| 1066 | | log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ symbol.name, exp.name, exp.index }); |
| 1144 | log.debug("Exporting symbol '{s}' as '{s}' at index: ({d})", .{ sym_name, exp.name, exp.index }); |
| 1067 | 1145 | try self.exports.append(self.base.allocator, exp); |
| 1068 | 1146 | } |
| 1069 | 1147 | |
| ... | ... | @@ -1670,8 +1748,8 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void { |
| 1670 | 1748 | for (self.resolved_symbols.keys()) |sym_loc| { |
| 1671 | 1749 | const symbol = sym_loc.getSymbol(self).*; |
| 1672 | 1750 | switch (symbol.tag) { |
| 1673 | | .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = mem.sliceTo(symbol.name, 0) }), |
| 1674 | | .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = mem.sliceTo(symbol.name, 0) }), |
| 1751 | .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = sym_loc.getName(self) }), |
| 1752 | .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = sym_loc.getName(self) }), |
| 1675 | 1753 | else => {}, |
| 1676 | 1754 | } |
| 1677 | 1755 | } |
| ... | ... | @@ -2275,11 +2353,11 @@ fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: * |
| 2275 | 2353 | try leb.writeULEB128(writer, @enumToInt(symbol.tag)); |
| 2276 | 2354 | try leb.writeULEB128(writer, symbol.flags); |
| 2277 | 2355 | |
| 2356 | const sym_name = if (self.export_names.get(sym_loc)) |exp_name| exp_name else sym_loc.getName(self); |
| 2278 | 2357 | switch (symbol.tag) { |
| 2279 | 2358 | .data => { |
| 2280 | | const name = mem.sliceTo(symbol.name, 0); |
| 2281 | | try leb.writeULEB128(writer, @intCast(u32, name.len)); |
| 2282 | | try writer.writeAll(name); |
| 2359 | try leb.writeULEB128(writer, @intCast(u32, sym_name.len)); |
| 2360 | try writer.writeAll(sym_name); |
| 2283 | 2361 | |
| 2284 | 2362 | if (symbol.isDefined()) { |
| 2285 | 2363 | try leb.writeULEB128(writer, symbol.index); |
| ... | ... | @@ -2294,9 +2372,8 @@ fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: * |
| 2294 | 2372 | else => { |
| 2295 | 2373 | try leb.writeULEB128(writer, symbol.index); |
| 2296 | 2374 | if (symbol.isDefined()) { |
| 2297 | | const name = mem.sliceTo(symbol.name, 0); |
| 2298 | | try leb.writeULEB128(writer, @intCast(u32, name.len)); |
| 2299 | | try writer.writeAll(name); |
| 2375 | try leb.writeULEB128(writer, @intCast(u32, sym_name.len)); |
| 2376 | try writer.writeAll(sym_name); |
| 2300 | 2377 | } |
| 2301 | 2378 | }, |
| 2302 | 2379 | } |