authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-28 12:49:04+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-28 12:49:04+01:00
log7226ad2670f267b4d90b84d0e104fbb1fa41fe49
tree2f690511466db3bc73e13b994fd3844079e321b6
parent9b5d61430fc7297b24d870adf42392ad113fa21b
signaturelock-open Commit is signed but in an unrecognized format.

wasm-link: Implement indirect function table

The function table contains all function pointers that are called by using call_indirect. During codegen, we create a relocation where the linker will resolve the correct index into the table and stores this value within the data section at the location of the pointer.

5 files changed, 105 insertions(+), 20 deletions(-)

src/arch/wasm/CodeGen.zig+26-4
...@@ -1065,9 +1065,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1065,9 +1065,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1065 const pl_op = self.air.instructions.items(.data)[inst].pl_op;1065 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1066 const extra = self.air.extraData(Air.Call, pl_op.payload);1066 const extra = self.air.extraData(Air.Call, pl_op.payload);
1067 const args = self.air.extra[extra.end..][0..extra.data.args_len];1067 const args = self.air.extra[extra.end..][0..extra.data.args_len];
1068 const ty = self.air.typeOf(pl_op.operand);
10681069
1069 const target: *Decl = blk: {1070 const fn_ty = switch (ty.zigTypeTag()) {
1070 const func_val = self.air.value(pl_op.operand).?;1071 .Fn => ty,
1072 .Pointer => ty.childType(),
1073 else => unreachable,
1074 };
1075
1076 const target: ?*Decl = blk: {
1077 const func_val = self.air.value(pl_op.operand) orelse break :blk null;
10711078
1072 if (func_val.castTag(.function)) |func| {1079 if (func_val.castTag(.function)) |func| {
1073 break :blk func.data.owner_decl;1080 break :blk func.data.owner_decl;
...@@ -1082,9 +1089,24 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1082,9 +1089,24 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1082 try self.emitWValue(arg_val);1089 try self.emitWValue(arg_val);
1083 }1090 }
10841091
1085 try self.addLabel(.call, target.link.wasm.sym_index);1092 if (target) |direct| {
1093 try self.addLabel(.call, direct.link.wasm.sym_index);
1094 } else {
1095 // in this case we call a function pointer
1096 // so load its value onto the stack
1097 std.debug.assert(ty.zigTypeTag() == .Pointer);
1098 const operand = self.resolveInst(pl_op.operand);
1099 const result = try self.load(operand, fn_ty, operand.local_with_offset.offset);
1100 try self.addLabel(.local_get, result.local);
1101
1102 var fn_type = try self.genFunctype(fn_ty);
1103 defer fn_type.deinit(self.gpa);
1104
1105 const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);
1106 try self.addLabel(.call_indirect, fn_type_index);
1107 }
10861108
1087 const ret_ty = target.ty.fnReturnType();1109 const ret_ty = fn_ty.fnReturnType();
1088 switch (ret_ty.zigTypeTag()) {1110 switch (ret_ty.zigTypeTag()) {
1089 .Void, .NoReturn => return WValue.none,1111 .Void, .NoReturn => return WValue.none,
1090 else => {1112 else => {
src/arch/wasm/Emit.zig+8
...@@ -47,6 +47,7 @@ pub fn emitMir(emit: *Emit) InnerError!void {...@@ -47,6 +47,7 @@ pub fn emitMir(emit: *Emit) InnerError!void {
4747
48 // relocatables48 // relocatables
49 .call => try emit.emitCall(inst),49 .call => try emit.emitCall(inst),
50 .call_indirect => try emit.emitCallIndirect(inst),
50 .global_get => try emit.emitGlobal(tag, inst),51 .global_get => try emit.emitGlobal(tag, inst),
51 .global_set => try emit.emitGlobal(tag, inst),52 .global_set => try emit.emitGlobal(tag, inst),
52 .memory_address => try emit.emitMemAddress(inst),53 .memory_address => try emit.emitMemAddress(inst),
...@@ -276,6 +277,13 @@ fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {...@@ -276,6 +277,13 @@ fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
276 });277 });
277}278}
278279
280fn emitCallIndirect(emit: *Emit, inst: Mir.Inst.Index) !void {
281 const label = emit.mir.instructions.items(.data)[inst].label;
282 try emit.code.append(std.wasm.opcode(.call_indirect));
283 try leb128.writeULEB128(emit.code.writer(), @as(u32, 0)); // TODO: Emit relocation for table index
284 try leb128.writeULEB128(emit.code.writer(), label);
285}
286
279fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {287fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
280 const symbol_index = emit.mir.instructions.items(.data)[inst].label;288 const symbol_index = emit.mir.instructions.items(.data)[inst].label;
281 try emit.code.append(std.wasm.opcode(.i32_const));289 try emit.code.append(std.wasm.opcode(.i32_const));
src/arch/wasm/Mir.zig+5
...@@ -69,6 +69,11 @@ pub const Inst = struct {...@@ -69,6 +69,11 @@ pub const Inst = struct {
69 ///69 ///
70 /// Uses `label`70 /// Uses `label`
71 call = 0x10,71 call = 0x10,
72 /// Calls a function pointer by its function signature
73 /// and index into the function table.
74 ///
75 /// Uses `label`
76 call_indirect = 0x11,
72 /// Loads a local at given index onto the stack.77 /// Loads a local at given index onto the stack.
73 ///78 ///
74 /// Uses `label`79 /// Uses `label`
src/link/Wasm.zig+65-15
...@@ -79,7 +79,9 @@ memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },...@@ -79,7 +79,9 @@ memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
79/// Indirect function table, used to call function pointers79/// Indirect function table, used to call function pointers
80/// When this is non-zero, we must emit a table entry,80/// When this is non-zero, we must emit a table entry,
81/// as well as an 'elements' section.81/// as well as an 'elements' section.
82function_table: std.ArrayListUnmanaged(Symbol) = .{},82///
83/// Note: Key is symbol index, value represents the index into the table
84function_table: std.AutoHashMapUnmanaged(u32, u32) = .{},
8385
84pub const Segment = struct {86pub const Segment = struct {
85 alignment: u32,87 alignment: u32,
...@@ -276,7 +278,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -276,7 +278,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
276 defer codegen.deinit();278 defer codegen.deinit();
277279
278 // generate the 'code' section for the function declaration280 // generate the 'code' section for the function declaration
279 const result = codegen.gen(decl.ty, decl.val) catch |err| switch (err) {281 const result = codegen.genDecl(decl.ty, decl.val) catch |err| switch (err) {
280 error.CodegenFail => {282 error.CodegenFail => {
281 decl.analysis = .codegen_failure;283 decl.analysis = .codegen_failure;
282 try module.failed_decls.put(module.gpa, decl, codegen.err_msg);284 try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
...@@ -334,6 +336,25 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {...@@ -334,6 +336,25 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
334 else => unreachable,336 else => unreachable,
335 }337 }
336 }338 }
339
340 // maybe remove from function table if needed
341 if (decl.ty.zigTypeTag() == .Fn) {
342 _ = self.function_table.remove(atom.sym_index);
343 }
344}
345
346/// Appends a new entry to the indirect function table
347pub fn addTableFunction(self: *Wasm, symbol_index: u32) !void {
348 const index = @intCast(u32, self.function_table.count());
349 try self.function_table.put(self.base.allocator, symbol_index, index);
350}
351
352fn mapFunctionTable(self: *Wasm) void {
353 var it = self.function_table.valueIterator();
354 var index: u32 = 0;
355 while (it.next()) |value_ptr| : (index += 1) {
356 value_ptr.* = index;
357 }
337}358}
338359
339fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {360fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {
...@@ -583,6 +604,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -583,6 +604,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
583604
584 try self.setupMemory();605 try self.setupMemory();
585 try self.allocateAtoms();606 try self.allocateAtoms();
607 self.mapFunctionTable();
586608
587 const file = self.base.file.?;609 const file = self.base.file.?;
588 const header_size = 5 + 1;610 const header_size = 5 + 1;
...@@ -662,6 +684,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -662,6 +684,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
662 );684 );
663 }685 }
664686
687 if (self.function_table.count() > 0) {
688 const header_offset = try reserveVecSectionHeader(file);
689 const writer = file.writer();
690
691 try leb.writeULEB128(writer, wasm.reftype(.funcref));
692 try emitLimits(writer, .{ .min = 1, .max = null });
693
694 try writeVecSectionHeader(
695 file,
696 header_offset,
697 .table,
698 @intCast(u32, (try file.getPos()) - header_offset - header_size),
699 @as(u32, 1),
700 );
701 }
702
665 // Memory section703 // Memory section
666 if (!self.base.options.import_memory) {704 if (!self.base.options.import_memory) {
667 const header_offset = try reserveVecSectionHeader(file);705 const header_offset = try reserveVecSectionHeader(file);
...@@ -743,6 +781,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -743,6 +781,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
743 );781 );
744 }782 }
745783
784 // element section (function table)
785 if (self.function_table.count() > 0) {
786 const header_offset = try reserveVecSectionHeader(file);
787 const writer = file.writer();
788
789 var flags: u32 = 0x2; // Yes we have a table
790 try leb.writeULEB128(writer, flags);
791 try leb.writeULEB128(writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols
792 try emitInit(writer, .{ .i32_const = 0 });
793 try leb.writeULEB128(writer, @as(u8, 0));
794 try leb.writeULEB128(writer, @intCast(u32, self.function_table.count()));
795 var symbol_it = self.function_table.keyIterator();
796 while (symbol_it.next()) |symbol_index_ptr| {
797 try leb.writeULEB128(writer, self.symbols.items[symbol_index_ptr.*].index);
798 }
799
800 try writeVecSectionHeader(
801 file,
802 header_offset,
803 .element,
804 @intCast(u32, (try file.getPos()) - header_offset - header_size),
805 @as(u32, 1),
806 );
807 }
808
746 // Code section809 // Code section
747 if (self.code_section_index) |code_index| {810 if (self.code_section_index) |code_index| {
748 const header_offset = try reserveVecSectionHeader(file);811 const header_offset = try reserveVecSectionHeader(file);
...@@ -1233,16 +1296,3 @@ pub fn putOrGetFuncType(self: *Wasm, func_type: wasm.Type) !u32 {...@@ -1233,16 +1296,3 @@ pub fn putOrGetFuncType(self: *Wasm, func_type: wasm.Type) !u32 {
1233 });1296 });
1234 return index;1297 return index;
1235}1298}
1236
1237/// From a given index and an `ExternalKind`, finds the corresponding Import.
1238/// This is due to indexes for imports being unique per type, rather than across all imports.
1239fn findImport(self: Wasm, index: u32, external_type: wasm.ExternalKind) ?*wasm.Import {
1240 var current_index: u32 = 0;
1241 for (self.imports.items) |*import| {
1242 if (import.kind == external_type) {
1243 if (current_index == index) return import;
1244 current_index += 1;
1245 }
1246 }
1247 return null;
1248}
src/link/Wasm/Atom.zig+1-1
...@@ -129,7 +129,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {...@@ -129,7 +129,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {
129 .R_WASM_TABLE_INDEX_I64,129 .R_WASM_TABLE_INDEX_I64,
130 .R_WASM_TABLE_INDEX_SLEB,130 .R_WASM_TABLE_INDEX_SLEB,
131 .R_WASM_TABLE_INDEX_SLEB64,131 .R_WASM_TABLE_INDEX_SLEB64,
132 => return error.TodoImplementTableIndex, // find table index from a function symbol132 => return wasm_bin.function_table.get(relocation.index) orelse 0,
133 .R_WASM_TYPE_INDEX_LEB => wasm_bin.functions.items[symbol.index].type_index,133 .R_WASM_TYPE_INDEX_LEB => wasm_bin.functions.items[symbol.index].type_index,
134 .R_WASM_GLOBAL_INDEX_I32,134 .R_WASM_GLOBAL_INDEX_I32,
135 .R_WASM_GLOBAL_INDEX_LEB,135 .R_WASM_GLOBAL_INDEX_LEB,