authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-24 21:12:27+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-27 15:02:05+01:00
loga54ac0888542d027b09e5c43729a7cfbe4a51393
tree432385a06a91e71f022e674a5d51f53c1974be9d
parentf56ae69edd8c96a5f6525f20bf0a22704a826f00
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: Resolve relocations

We now resolve relocations for globals, memory addresses and function indexes. Besides above, we now also emit imported functions correctly and create a corresponding undefined symbol for it, where as we create a defined symbol for all other cases. TODO: Make incrememental compilation work again with new linker infrastructure

5 files changed, 115 insertions(+), 86 deletions(-)

src/arch/wasm/CodeGen.zig+1-10
......@@ -1363,16 +1363,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
13631363 if (val.castTag(.decl_ref)) |payload| {
13641364 const decl = payload.data;
13651365 decl.alive = true;
1366
1367 // offset into the offset table within the 'data' section
1368 // const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
1369 // try self.addImm32(@bitCast(i32, decl.link.wasm.offset_index * ptr_width));
1370
1371 // memory instruction followed by their memarg immediate
1372 // memarg ::== x:u32, y:u32 => {align x, offset y}
1373 const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 });
1374 try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
1375 @panic("REDO!\n");
1366 try self.addLabel(.memory_address, decl.link.wasm.sym_index);
13761367 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
13771368 },
13781369 .Void => {},
src/arch/wasm/Emit.zig+28-6
......@@ -49,6 +49,7 @@ pub fn emitMir(emit: *Emit) InnerError!void {
4949 .call => try emit.emitCall(inst),
5050 .global_get => try emit.emitGlobal(tag, inst),
5151 .global_set => try emit.emitGlobal(tag, inst),
52 .memory_address => try emit.emitMemAddress(inst),
5253
5354 // immediates
5455 .f32_const => try emit.emitFloat32(inst),
......@@ -157,6 +158,10 @@ pub fn emitMir(emit: *Emit) InnerError!void {
157158 }
158159}
159160
161fn offset(self: Emit) u32 {
162 return @intCast(u32, self.code.items.len);
163}
164
160165fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
161166 @setCold(true);
162167 std.debug.assert(emit.error_msg == null);
......@@ -209,9 +214,14 @@ fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
209214 try emit.code.append(@enumToInt(tag));
210215 var buf: [5]u8 = undefined;
211216 leb128.writeUnsignedFixed(5, &buf, label);
217 const global_offset = emit.offset();
212218 try emit.code.appendSlice(&buf);
213219
214 // TODO: Append label to the relocation list of this function
220 try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
221 .index = label,
222 .offset = global_offset,
223 .relocation_type = .R_WASM_GLOBAL_INDEX_LEB,
224 });
215225}
216226
217227fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void {
......@@ -254,17 +264,29 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
254264fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
255265 const label = emit.mir.instructions.items(.data)[inst].label;
256266 try emit.code.append(std.wasm.opcode(.call));
257 const offset = @intCast(u32, emit.code.items.len);
267 const call_offset = emit.offset();
258268 var buf: [5]u8 = undefined;
259269 leb128.writeUnsignedFixed(5, &buf, label);
260270 try emit.code.appendSlice(&buf);
261271
262 // The function index immediate argument will be filled in using this data
263 // in link.Wasm.flush().
264 // TODO: Replace this with proper relocations saved in the Atom.
265272 try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
266 .offset = offset,
273 .offset = call_offset,
267274 .index = label,
268275 .relocation_type = .R_WASM_FUNCTION_INDEX_LEB,
269276 });
270277}
278
279fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
280 const symbol_index = emit.mir.instructions.items(.data)[inst].label;
281 try emit.code.append(std.wasm.opcode(.i32_const));
282 const mem_offset = emit.offset();
283 var buf: [5]u8 = undefined;
284 leb128.writeUnsignedFixed(5, &buf, symbol_index);
285 try emit.code.appendSlice(&buf);
286
287 try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
288 .offset = mem_offset,
289 .index = symbol_index,
290 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
291 });
292}
src/arch/wasm/Mir.zig+6
......@@ -358,6 +358,12 @@ pub const Inst = struct {
358358 i64_extend16_s = 0xC3,
359359 /// Uses `tag`
360360 i64_extend32_s = 0xC4,
361 /// Contains a symbol to a memory address
362 /// Uses `label`
363 ///
364 /// Note: This uses `0xFF` as value as it is unused and not-reserved
365 /// by the wasm specification, making it safe to use
366 memory_address = 0xFF,
361367
362368 /// From a given wasm opcode, returns a MIR tag.
363369 pub fn fromOpcode(opcode: std.wasm.Opcode) Tag {
src/link/Wasm.zig+64-52
......@@ -301,10 +301,66 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, cod
301301 // to avoid infinite loops due to earlier links
302302 atom.unplug();
303303
304 const symbol: *Symbol = &self.symbols.items[atom.sym_index];
305304 if (decl.isExtern()) {
306 symbol.setUndefined(true);
305 try self.createUndefinedSymbol(decl, atom.sym_index);
306 } else {
307 try self.createDefinedSymbol(decl, atom.sym_index, atom);
308 }
309}
310
311pub fn updateDeclExports(
312 self: *Wasm,
313 module: *Module,
314 decl: *const Module.Decl,
315 exports: []const *Module.Export,
316) !void {
317 if (build_options.skip_non_native and builtin.object_format != .wasm) {
318 @panic("Attempted to compile for object format that was disabled by build configuration");
319 }
320 if (build_options.have_llvm) {
321 if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
322 }
323}
324
325pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
326 if (build_options.have_llvm) {
327 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);
328 }
329
330 const atom = &decl.link.wasm;
331
332 if (self.last_atom == atom) {
333 self.last_atom = atom.prev;
307334 }
335
336 atom.unplug();
337 self.symbols_free_list.append(self.base.allocator, atom.sym_index) catch {};
338 atom.deinit(self.base.allocator);
339 _ = self.decls.remove(decl);
340}
341
342fn createUndefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32) !void {
343 var symbol: *Symbol = &self.symbols.items[symbol_index];
344 symbol.name = decl.name;
345 symbol.setUndefined(true);
346 switch (decl.ty.zigTypeTag()) {
347 .Fn => {
348 symbol.index = self.imported_functions_count;
349 self.imported_functions_count += 1;
350 try self.import_symbols.append(self.base.allocator, symbol_index);
351 try self.imports.append(self.base.allocator, .{
352 .module_name = self.host_name,
353 .name = std.mem.span(decl.name),
354 .kind = .{ .function = decl.fn_link.wasm.type_index },
355 });
356 },
357 else => @panic("TODO: Implement undefined symbols for non-function declarations"),
358 }
359}
360
361/// Creates a defined symbol, as well as inserts the given `atom` into the chain
362fn createDefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32, atom: *Atom) !void {
363 const symbol: *Symbol = &self.symbols.items[symbol_index];
308364 symbol.name = decl.name;
309365 const final_index = switch (decl.ty.zigTypeTag()) {
310366 .Fn => result: {
......@@ -371,49 +427,6 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, cod
371427 }
372428}
373429
374pub fn updateDeclExports(
375 self: *Wasm,
376 module: *Module,
377 decl: *const Module.Decl,
378 exports: []const *Module.Export,
379) !void {
380 if (build_options.skip_non_native and builtin.object_format != .wasm) {
381 @panic("Attempted to compile for object format that was disabled by build configuration");
382 }
383 if (build_options.have_llvm) {
384 if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl, exports);
385 }
386}
387
388pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
389 if (build_options.have_llvm) {
390 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);
391 }
392
393 const atom = &decl.link.wasm;
394
395 if (self.last_atom == atom) {
396 self.last_atom = atom.prev;
397 }
398
399 atom.unplug();
400 self.symbols_free_list.append(self.base.allocator, atom.sym_index) catch {};
401 atom.deinit(self.base.allocator);
402 _ = self.decls.remove(decl);
403}
404
405fn createUndefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32) !void {
406 var symbol: *Symbol = &self.symbols.items[symbol_index];
407 symbol.setUndefined(true);
408 switch (decl.ty.zigTypeTag()) {
409 .Fn => {
410 symbol.setIndex(self.imported_functions_count);
411 self.imported_functions_count += 1;
412 },
413 else => @panic("TODO: Wasm implement extern non-function types"),
414 }
415}
416
417430pub fn flush(self: *Wasm, comp: *Compilation) !void {
418431 if (build_options.have_llvm and self.base.options.use_lld) {
419432 return self.linkWithLLD(comp);
......@@ -442,7 +455,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
442455 }
443456
444457 // set the stack size on the global
445 self.globals.items[0].init = .{ .i32_const = @bitCast(i32, data_size + stack_size) };
458 self.globals.items[0].init.i32_const = @bitCast(i32, data_size + stack_size);
446459
447460 // No need to rewrite the magic/version header
448461 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
......@@ -515,10 +528,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
515528 const header_offset = try reserveVecSectionHeader(file);
516529 const writer = file.writer();
517530 for (self.functions.items) |function| {
518 try leb.writeULEB128(
519 writer,
520 @intCast(u32, function.type_index),
521 );
531 try leb.writeULEB128(writer, function.type_index);
522532 }
523533
524534 try writeVecSectionHeader(
......@@ -580,7 +590,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
580590 const header_offset = try reserveVecSectionHeader(file);
581591 const writer = file.writer();
582592 var count: u32 = 0;
583 var func_index: u32 = 0;
593 var func_index: u32 = self.imported_functions_count;
584594 for (module.decl_exports.values()) |exports| {
585595 for (exports) |exprt| {
586596 // Export name length + name
......@@ -624,8 +634,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
624634 if (self.code_section_index) |code_index| {
625635 const header_offset = try reserveVecSectionHeader(file);
626636 const writer = file.writer();
627 var atom = self.atoms.get(code_index).?.getFirst();
637 var atom: *Atom = self.atoms.get(code_index).?.getFirst();
628638 while (true) {
639 try atom.resolveRelocs(self);
629640 try leb.writeULEB128(writer, atom.size);
630641 try writer.writeAll(atom.code.items);
631642
......@@ -667,6 +678,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
667678 // fill in the offset table and the data segments
668679 var current_offset: u32 = 0;
669680 while (true) {
681 try atom.resolveRelocs(self);
670682 std.debug.assert(current_offset == atom.offset);
671683 std.debug.assert(atom.code.items.len == atom.size);
672684
src/link/Wasm/Atom.zig+16-18
......@@ -72,6 +72,7 @@ pub fn getLast(self: *Atom) *Atom {
7272 while (tmp.next) |next| tmp = next;
7373 return tmp;
7474}
75
7576/// Unplugs the `Atom` from the chain
7677pub fn unplug(self: *Atom) void {
7778 if (self.prev) |prev| {
......@@ -88,18 +89,16 @@ pub fn unplug(self: *Atom) void {
8889/// Resolves the relocations within the atom, writing the new value
8990/// at the calculated offset.
9091pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
91 const object = wasm_bin.objects.items[self.file];
92 const symbol: Symbol = object.symtable[self.sym_index];
93
92 const symbol: Symbol = wasm_bin.symbols.items[self.sym_index];
9493 log.debug("Resolving relocs in atom '{s}' count({d})", .{
9594 symbol.name,
9695 self.relocs.items.len,
9796 });
9897
9998 for (self.relocs.items) |reloc| {
100 const value = self.relocationValue(reloc, wasm_bin);
101 log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{
102 object.symtable[reloc.index].name,
99 const value = try relocationValue(reloc, wasm_bin);
100 log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}\n", .{
101 wasm_bin.symbols.items[reloc.index].name,
103102 symbol.name,
104103 reloc.offset,
105104 value,
......@@ -135,21 +134,20 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
135134/// From a given `relocation` will return the new value to be written.
136135/// All values will be represented as a `u64` as all values can fit within it.
137136/// The final value must be casted to the correct size.
138fn relocationValue(self: *Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
139 const object = wasm_bin.objects.items[self.file];
140 const symbol: Symbol = object.symtable[relocation.index];
137fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {
138 const symbol: Symbol = wasm_bin.symbols.items[relocation.index];
141139 return switch (relocation.relocation_type) {
142 .R_WASM_FUNCTION_INDEX_LEB => symbol.kind.function.functionIndex(),
143 .R_WASM_TABLE_NUMBER_LEB => symbol.kind.table.table.table_idx,
140 .R_WASM_FUNCTION_INDEX_LEB => symbol.index,
141 .R_WASM_TABLE_NUMBER_LEB => symbol.index,
144142 .R_WASM_TABLE_INDEX_I32,
145143 .R_WASM_TABLE_INDEX_I64,
146144 .R_WASM_TABLE_INDEX_SLEB,
147145 .R_WASM_TABLE_INDEX_SLEB64,
148 => symbol.getTableIndex() orelse 0,
149 .R_WASM_TYPE_INDEX_LEB => symbol.kind.function.func.type_idx,
146 => return error.TodoImplementTableIndex, // find table index from a function symbol
147 .R_WASM_TYPE_INDEX_LEB => wasm_bin.functions.items[symbol.index].type_index,
150148 .R_WASM_GLOBAL_INDEX_I32,
151149 .R_WASM_GLOBAL_INDEX_LEB,
152 => symbol.kind.global.global.global_idx,
150 => symbol.index,
153151 .R_WASM_MEMORY_ADDR_I32,
154152 .R_WASM_MEMORY_ADDR_I64,
155153 .R_WASM_MEMORY_ADDR_LEB,
......@@ -157,10 +155,10 @@ fn relocationValue(self: *Atom, relocation: types.Relocation, wasm_bin: *const W
157155 .R_WASM_MEMORY_ADDR_SLEB,
158156 .R_WASM_MEMORY_ADDR_SLEB64,
159157 => blk: {
160 if (symbol.isUndefined() and (symbol.kind == .data or symbol.isWeak())) {
158 if (symbol.isUndefined() and (symbol.tag == .data or symbol.isWeak())) {
161159 return 0;
162160 }
163 const segment_name = object.segment_info[symbol.index().?].outputName();
161 const segment_name = wasm_bin.segment_info.items[symbol.index].outputName();
164162 const atom_index = wasm_bin.data_segments.get(segment_name).?;
165163 var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();
166164 while (true) {
......@@ -170,11 +168,11 @@ fn relocationValue(self: *Atom, relocation: types.Relocation, wasm_bin: *const W
170168 } else break;
171169 }
172170 const segment = wasm_bin.segments.items[atom_index];
173 const base = wasm_bin.options.global_base orelse 1024;
171 const base = wasm_bin.base.options.global_base orelse 0;
174172 const offset = target_atom.offset + segment.offset;
175173 break :blk offset + base + (relocation.addend orelse 0);
176174 },
177 .R_WASM_EVENT_INDEX_LEB => symbol.kind.event.index,
175 .R_WASM_EVENT_INDEX_LEB => symbol.index,
178176 .R_WASM_SECTION_OFFSET_I32,
179177 .R_WASM_FUNCTION_OFFSET_I32,
180178 => relocation.offset,