authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-02-19 21:25:50+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-02-23 16:07:36+01:00
log0a48a763fd3548a7b8609555cecbafc21ebe1fc3
treeefcb52976b8182f51b7c1d80ce88bb5b6997c5a7
parent2b0431a8d3933a46fecba2bf064bf45df54a111d

wasm-linker: Emit relocations for object files

When generating a relocatable object file, we now emit a custom "reloc.CODE" and "reloc.DATA" section which will contain the relocations for each section. Using a new symbol location -> Atom mapping, we can now easily find the corresponding `Atom` from a symbol. This can be used to construct the symbol table, as well as easier access to a target atom when performing a relocation for a data symbol.

2 files changed, 174 insertions(+), 58 deletions(-)

src/link/Wasm.zig+166-45
......@@ -110,6 +110,10 @@ discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
110110/// List of all symbol locations which have been resolved by the linker and will be emit
111111/// into the final binary.
112112resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{},
113/// Maps a symbol's location to an atom. This can be used to find meta
114/// data of a symbol, such as its size, or its offset to perform a relocation.
115/// Undefined (and synthetic) symbols do not have an Atom and therefore cannot be mapped.
116symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, *Atom) = .{},
113117
114118pub const Segment = struct {
115119 alignment: u32,
......@@ -170,6 +174,10 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
170174 .flags = 0,
171175 .index = 0,
172176 };
177 const loc: SymbolLoc = .{ .file = null, .index = 0 };
178 try wasm_bin.resolved_symbols.putNoClobber(allocator, loc, {});
179 try wasm_bin.globals.putNoClobber(allocator, "__stack_pointer", loc);
180
173181 // For object files we will import the stack pointer symbol
174182 if (options.output_mode == .Obj) {
175183 symbol.setUndefined(true);
......@@ -336,6 +344,7 @@ pub fn deinit(self: *Wasm) void {
336344 self.globals.deinit(gpa);
337345 self.resolved_symbols.deinit(gpa);
338346 self.discarded.deinit(gpa);
347 self.symbol_atom.deinit(gpa);
339348 self.atoms.deinit(gpa);
340349 self.managed_atoms.deinit(gpa);
341350 self.segments.deinit(gpa);
......@@ -506,6 +515,10 @@ pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type) !u32 {
506515 atom.sym_index = @intCast(u32, self.symbols.items.len);
507516 self.symbols.appendAssumeCapacity(symbol);
508517 }
518 try self.resolved_symbols.putNoClobber(self.base.allocator, .{
519 .file = null,
520 .index = atom.sym_index,
521 }, {});
509522
510523 try decl.link.wasm.locals.append(self.base.allocator, atom);
511524 return atom.sym_index;
......@@ -614,12 +627,13 @@ fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {
614627 const symbol: *Symbol = &self.symbols.items[symbol_index];
615628 symbol.name = decl.name;
616629 symbol.setUndefined(true);
617 // also add it as a global so it can be resolved
618630 try self.globals.putNoClobber(
619631 self.base.allocator,
620632 mem.sliceTo(symbol.name, 0),
621633 .{ .file = null, .index = symbol_index },
622634 );
635 try self.resolved_symbols.put(self.base.allocator, .{ .file = null, .index = symbol_index }, {});
636
623637 switch (decl.ty.zigTypeTag()) {
624638 .Fn => {
625639 const gop = try self.imports.getOrPut(self.base.allocator, .{ .index = symbol_index, .file = null });
......@@ -730,20 +744,22 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
730744}
731745
732746fn allocateAtoms(self: *Wasm) !void {
733 var it = self.atoms.iterator();
734 while (it.next()) |entry| {
735 var atom: *Atom = entry.value_ptr.*.getFirst();
747 var it = self.atoms.valueIterator();
748 while (it.next()) |current_atom| {
749 var atom: *Atom = current_atom.*.getFirst();
736750 var offset: u32 = 0;
737751 while (true) {
738752 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
739753 atom.offset = offset;
754 const symbol_loc = atom.symbolLoc();
740755 log.debug("Atom '{s}' allocated from 0x{x:0>8} to 0x{x:0>8} size={d}", .{
741 (SymbolLoc{ .file = atom.file, .index = atom.sym_index }).getSymbol(self).name,
756 symbol_loc.getSymbol(self).name,
742757 offset,
743758 offset + atom.size,
744759 atom.size,
745760 });
746761 offset += atom.size;
762 try self.symbol_atom.putNoClobber(self.base.allocator, symbol_loc, atom);
747763 atom = atom.next orelse break;
748764 }
749765 }
......@@ -1295,7 +1311,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
12951311 }
12961312
12971313 // Global section (used to emit stack pointer)
1298 {
1314 if (self.wasm_globals.items.len > 0) {
12991315 const header_offset = try reserveVecSectionHeader(file);
13001316 const writer = file.writer();
13011317
......@@ -1480,7 +1496,13 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
14801496 }
14811497
14821498 if (is_obj) {
1483 try self.emitLinkSection(file, arena);
1499 // relocations need to point to the index of a symbol in the final symbol table. To save memory,
1500 // we never store all symbols in a single table, but store a location reference instead.
1501 // This means that for a relocatable object file, we need to generate one and provide it to the relocation sections.
1502 var symbol_table = std.AutoArrayHashMap(SymbolLoc, u32).init(arena);
1503 try self.emitLinkSection(file, arena, &symbol_table);
1504 try self.emitCodeRelocations(file, arena, 6, symbol_table);
1505 try self.emitDataRelocations(file, arena, 7, symbol_table);
14841506 } else {
14851507 try self.emitNameSection(file, arena);
14861508 }
......@@ -1498,7 +1520,7 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
14981520 };
14991521
15001522 var funcs = try std.ArrayList(Name).initCapacity(arena, self.functions.items.len + self.imported_functions_count);
1501 var globals = try std.ArrayList(Name).initCapacity(arena, self.wasm_globals.items.len);
1523 var globals = try std.ArrayList(Name).initCapacity(arena, self.wasm_globals.items.len + self.imported_globals_count);
15021524 var segments = try std.ArrayList(Name).initCapacity(arena, self.data_segments.count());
15031525
15041526 for (self.resolved_symbols.keys()) |sym_loc| {
......@@ -2069,7 +2091,7 @@ fn writeCustomSectionHeader(file: fs.File, offset: u64, size: u32) !void {
20692091 try file.pwriteAll(&buf, offset);
20702092}
20712093
2072fn emitLinkSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
2094fn emitLinkSection(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
20732095 const offset = try reserveCustomSectionHeader(file);
20742096 const writer = file.writer();
20752097 // emit "linking" custom section name
......@@ -2082,14 +2104,14 @@ fn emitLinkSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
20822104
20832105 // For each subsection type (found in types.Subsection) we can emit a section.
20842106 // Currently, we only support emitting segment info and the symbol table.
2085 try self.emitSymbolTable(file, arena);
2107 try self.emitSymbolTable(file, arena, symbol_table);
20862108 try self.emitSegmentInfo(file, arena);
20872109
20882110 const size = @intCast(u32, (try file.getPos()) - offset - 6);
20892111 try writeCustomSectionHeader(file, offset, size);
20902112}
20912113
2092fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator) !void {
2114fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
20932115 // After emitting the subtype, we must emit the subsection's length
20942116 // so first write it to a temporary arraylist to calculate the length
20952117 // and then write all data at once.
......@@ -2099,43 +2121,39 @@ fn emitSymbolTable(self: *Wasm, file: fs.File, arena: Allocator) !void {
20992121 try leb.writeULEB128(file.writer(), @enumToInt(types.SubsectionType.WASM_SYMBOL_TABLE));
21002122
21012123 var symbol_count: u32 = 0;
2102 var atom_it = self.atoms.valueIterator();
2103 while (atom_it.next()) |next_atom| {
2104 var atom: ?*Atom = next_atom.*.getFirst();
2105 while (atom) |current_atom| {
2106 const sym_loc: SymbolLoc = .{ .file = current_atom.file, .index = current_atom.sym_index };
2107 const symbol = sym_loc.getSymbol(self).*;
2108 if (symbol.tag == .dead) continue; // Do not emit dead symbols
2109 symbol_count += 1;
2110 log.debug("Emit symbol: {}", .{symbol});
2111 try leb.writeULEB128(writer, @enumToInt(symbol.tag));
2112 try leb.writeULEB128(writer, symbol.flags);
2113
2114 switch (symbol.tag) {
2115 .data => {
2124 for (self.resolved_symbols.keys()) |sym_loc| {
2125 const symbol = sym_loc.getSymbol(self).*;
2126 if (symbol.tag == .dead) continue; // Do not emit dead symbols
2127 try symbol_table.putNoClobber(sym_loc, symbol_count);
2128 symbol_count += 1;
2129 log.debug("Emit symbol: {}", .{symbol});
2130 try leb.writeULEB128(writer, @enumToInt(symbol.tag));
2131 try leb.writeULEB128(writer, symbol.flags);
2132
2133 switch (symbol.tag) {
2134 .data => {
2135 const name = mem.sliceTo(symbol.name, 0);
2136 try leb.writeULEB128(writer, @intCast(u32, name.len));
2137 try writer.writeAll(name);
2138
2139 if (symbol.isDefined()) {
2140 try leb.writeULEB128(writer, symbol.index);
2141 const atom = self.symbol_atom.get(sym_loc).?;
2142 try leb.writeULEB128(writer, @as(u32, atom.offset));
2143 try leb.writeULEB128(writer, @as(u32, atom.size));
2144 }
2145 },
2146 .section => {
2147 try leb.writeULEB128(writer, symbol.index);
2148 },
2149 else => {
2150 try leb.writeULEB128(writer, symbol.index);
2151 if (symbol.isDefined()) {
21162152 const name = mem.sliceTo(symbol.name, 0);
21172153 try leb.writeULEB128(writer, @intCast(u32, name.len));
21182154 try writer.writeAll(name);
2119
2120 if (symbol.isDefined()) {
2121 try leb.writeULEB128(writer, symbol.index);
2122 try leb.writeULEB128(writer, @as(u32, current_atom.offset));
2123 try leb.writeULEB128(writer, @as(u32, current_atom.size));
2124 }
2125 },
2126 .section => {
2127 try leb.writeULEB128(writer, symbol.index);
2128 },
2129 else => {
2130 try leb.writeULEB128(writer, symbol.index);
2131 if (symbol.isDefined()) {
2132 const name = mem.sliceTo(symbol.name, 0);
2133 try leb.writeULEB128(writer, @intCast(u32, name.len));
2134 try writer.writeAll(name);
2135 }
2136 },
2137 }
2138 atom = current_atom.next orelse break;
2155 }
2156 },
21392157 }
21402158 }
21412159
......@@ -2176,6 +2194,109 @@ fn emitSegmentInfo(self: *Wasm, file: fs.File, arena: Allocator) !void {
21762194 try file.writevAll(&.{iovec});
21772195}
21782196
2197/// For each relocatable section, emits a custom "relocation.<section_name>" section
2198fn emitCodeRelocations(
2199 self: *Wasm,
2200 file: fs.File,
2201 arena: Allocator,
2202 section_index: u32,
2203 symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
2204) !void {
2205 const code_index = self.code_section_index orelse return;
2206 var payload = std.ArrayList(u8).init(arena);
2207 const writer = payload.writer();
2208
2209 // write custom section information
2210 const name = "reloc.CODE";
2211 try leb.writeULEB128(writer, @intCast(u32, name.len));
2212 try writer.writeAll(name);
2213 try leb.writeULEB128(writer, section_index);
2214 const reloc_start = payload.items.len;
2215
2216 var count: u32 = 0;
2217 var atom: *Atom = self.atoms.get(code_index).?.getFirst();
2218 while (true) {
2219 for (atom.relocs.items) |relocation| {
2220 count += 1;
2221 const sym_loc: SymbolLoc = .{ .file = atom.file, .index = relocation.index };
2222 const symbol_index = symbol_table.get(sym_loc).?;
2223 try leb.writeULEB128(writer, @enumToInt(relocation.relocation_type));
2224 try leb.writeULEB128(writer, atom.offset + relocation.offset);
2225 try leb.writeULEB128(writer, symbol_index);
2226 if (relocation.relocation_type.addendIsPresent()) {
2227 try leb.writeULEB128(writer, relocation.addend orelse 0);
2228 }
2229 }
2230 atom = atom.next orelse break;
2231 }
2232 if (count == 0) return;
2233 var buf: [5]u8 = undefined;
2234 leb.writeUnsignedFixed(5, &buf, count);
2235 try payload.insertSlice(reloc_start, &buf);
2236 const iovec: std.os.iovec_const = .{
2237 .iov_base = payload.items.ptr,
2238 .iov_len = payload.items.len,
2239 };
2240 const header_offset = try reserveCustomSectionHeader(file);
2241 try file.writevAll(&.{iovec});
2242 const size = @intCast(u32, payload.items.len);
2243 try writeCustomSectionHeader(file, header_offset, size);
2244}
2245
2246fn emitDataRelocations(
2247 self: *Wasm,
2248 file: fs.File,
2249 arena: Allocator,
2250 section_index: u32,
2251 symbol_table: std.AutoArrayHashMap(SymbolLoc, u32),
2252) !void {
2253 if (self.data_segments.count() == 0) return;
2254 var payload = std.ArrayList(u8).init(arena);
2255 const writer = payload.writer();
2256
2257 // write custom section information
2258 const name = "reloc.DATA";
2259 try leb.writeULEB128(writer, @intCast(u32, name.len));
2260 try writer.writeAll(name);
2261 try leb.writeULEB128(writer, section_index);
2262 const reloc_start = payload.items.len;
2263
2264 var count: u32 = 0;
2265 for (self.data_segments.values()) |segment_index| {
2266 var atom: *Atom = self.atoms.get(segment_index).?.getFirst();
2267 while (true) {
2268 for (atom.relocs.items) |relocation| {
2269 count += 1;
2270 const sym_loc: SymbolLoc = .{
2271 .file = atom.file,
2272 .index = relocation.index,
2273 };
2274 const symbol_index = symbol_table.get(sym_loc).?;
2275 try leb.writeULEB128(writer, @enumToInt(relocation.relocation_type));
2276 try leb.writeULEB128(writer, atom.offset + relocation.offset);
2277 try leb.writeULEB128(writer, symbol_index);
2278 if (relocation.relocation_type.addendIsPresent()) {
2279 try leb.writeULEB128(writer, relocation.addend orelse 0);
2280 }
2281 }
2282 atom = atom.next orelse break;
2283 }
2284 }
2285 if (count == 0) return;
2286
2287 var buf: [5]u8 = undefined;
2288 leb.writeUnsignedFixed(5, &buf, count);
2289 try payload.insertSlice(reloc_start, &buf);
2290 const iovec: std.os.iovec_const = .{
2291 .iov_base = payload.items.ptr,
2292 .iov_len = payload.items.len,
2293 };
2294 const header_offset = try reserveCustomSectionHeader(file);
2295 try file.writevAll(&.{iovec});
2296 const size = @intCast(u32, payload.items.len);
2297 try writeCustomSectionHeader(file, header_offset, size);
2298}
2299
21792300/// Searches for an a matching function signature, when not found
21802301/// a new entry will be made. The index of the existing/new signature will be returned.
21812302pub fn putOrGetFuncType(self: *Wasm, func_type: wasm.Type) !u32 {
src/link/Wasm/Atom.zig+8-13
......@@ -94,12 +94,16 @@ pub fn symbolAtom(self: *Atom, symbol_index: u32) *Atom {
9494 } else unreachable; // Used a symbol index not present in this atom or its children.
9595}
9696
97/// Returns the location of the symbol that represents this `Atom`
98pub fn symbolLoc(self: Atom) Wasm.SymbolLoc {
99 return .{ .file = self.file, .index = self.sym_index };
100}
101
97102/// Resolves the relocations within the atom, writing the new value
98103/// at the calculated offset.
99104pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
100105 if (self.relocs.items.len == 0) return;
101 const loc: Wasm.SymbolLoc = .{ .file = self.file, .index = self.sym_index };
102 const symbol = loc.getSymbol(wasm_bin).*;
106 const symbol = self.symbolLoc().getSymbol(wasm_bin).*;
103107 log.debug("Resolving relocs in atom '{s}' count({d})", .{
104108 symbol.name,
105109 self.relocs.items.len,
......@@ -169,20 +173,11 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa
169173 if (symbol.isUndefined() and (symbol.tag == .data or symbol.isWeak())) {
170174 return 0;
171175 }
176
172177 const merge_segment = wasm_bin.base.options.output_mode != .Obj;
173178 const segment_name = wasm_bin.segment_info.items[symbol.index].outputName(merge_segment);
174179 const atom_index = wasm_bin.data_segments.get(segment_name).?;
175 var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();
176 while (true) {
177 // TODO: Can we simplify this by providing the ability to find an atom
178 // based on a symbol location.
179 if (target_atom.sym_index == relocation.index) {
180 if (target_atom.file) |file| {
181 if (self.file != null and self.file.? == file) break;
182 } else if (self.file == null) break;
183 }
184 target_atom = target_atom.next orelse break;
185 }
180 const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
186181 const segment = wasm_bin.segments.items[atom_index];
187182 return target_atom.offset + segment.offset + (relocation.addend orelse 0);
188183 },