| author | |
| committer | |
| log | 7226ad2670f267b4d90b84d0e104fbb1fa41fe49 |
| tree | 2f690511466db3bc73e13b994fd3844079e321b6 |
| parent | 9b5d61430fc7297b24d870adf42392ad113fa21b |
| signature |
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); | ||
| 1068 | 1069 | ||
| 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; | ||
| 1071 | 1078 | ||
| 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 | } |
| 1084 | 1091 | ||
| 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 | } | ||
| 1086 | 1108 | ||
| 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 { |
| 47 | 47 | ||
| 48 | // relocatables | 48 | // 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 | } |
| 278 | 279 | ||
| 280 | fn 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 | |||
| 279 | fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void { | 287 | fn 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 pointers | 79 | /// 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. |
| 82 | function_table: std.ArrayListUnmanaged(Symbol) = .{}, | 82 | /// |
| 83 | /// Note: Key is symbol index, value represents the index into the table | ||
| 84 | function_table: std.AutoHashMapUnmanaged(u32, u32) = .{}, | ||
| 83 | 85 | ||
| 84 | pub const Segment = struct { | 86 | pub 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(); |
| 277 | 279 | ||
| 278 | // generate the 'code' section for the function declaration | 280 | // 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 | ||
| 347 | pub 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 | |||
| 352 | fn 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 | } |
| 338 | 359 | ||
| 339 | fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void { | 360 | fn 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 { |
| 583 | 604 | ||
| 584 | try self.setupMemory(); | 605 | try self.setupMemory(); |
| 585 | try self.allocateAtoms(); | 606 | try self.allocateAtoms(); |
| 607 | self.mapFunctionTable(); | ||
| 586 | 608 | ||
| 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 | } |
| 664 | 686 | ||
| 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 section | 703 | // 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 | } |
| 745 | 783 | ||
| 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 section | 809 | // 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. | ||
| 1239 | fn 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 symbol | 132 | => 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, |