From 49051c065120781b6ff78172c447b6307bd01e39 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 22 Mar 2022 21:20:36 +0100 Subject: wasm: Implement `@errorName` This implements the `error_name` instruction, which is emit for runtime `@errorName` callsites. The implementation works by creating 2 symbols and corresponding atoms. The initial symbol contains a table which each element consisting of a slice where the ptr field points towards the error name, and the len field contains the error name length without the sentinel. The secondary symbol contains a list of all error names from the global error set. During the error_name instruction, we first get a pointer to the first symbol. Then based on the operand we perform pointer arithmetic, to get the correct index into this table. e.g. error index 2 = ptr + (2 * ptr size). The result of this will be stored in a local and then returned as instruction result. During `flush()` we populate the error names table by looping over the global error set and creating a relocation for each error name. This relocation is appended to the table symbol. Then finally, this name is written to the names list itself. Finally, both symbols' atom are allocated within the rest of the binary. When no error name is referenced, the `error_name_symbol` is never set, and therefore no error name table will be emit into the final binary. --- src/arch/wasm/CodeGen.zig | 45 +++++++++++++++- src/link/Wasm.zig | 127 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index eb4004fa2c..136ef5cc45 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1403,6 +1403,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .wrap_errunion_payload => self.airWrapErrUnionPayload(inst), .wrap_errunion_err => self.airWrapErrUnionErr(inst), .errunion_payload_ptr_set => self.airErrUnionPayloadPtrSet(inst), + .error_name => self.airErrorName(inst), .wasm_memory_size => self.airWasmMemorySize(inst), .wasm_memory_grow => self.airWasmMemoryGrow(inst), @@ -1458,7 +1459,6 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .atomic_store_seq_cst, .atomic_rmw, .tag_name, - .error_name, .mul_add, // For these 4, probably best to wait until https://github.com/ziglang/zig/issues/10248 @@ -3618,3 +3618,46 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) InnerError!WValue { try self.addLabel(.local_set, result.local); return result; } + +fn airErrorName(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + + // First retrieve the symbol index to the error name table + // that will be used to emit a relocation for the pointer + // to the error name table. + // + // Each entry to this table is a slice (ptr+len). + // The operand in this instruction represents the index within this table. + // This means to get the final name, we emit the base pointer and then perform + // pointer arithmetic to find the pointer to this slice and return that. + // + // As the names are global and the slice elements are constant, we do not have + // to make a copy of the ptr+value but can point towards them directly. + const error_table_symbol = try self.bin_file.getErrorTableSymbol(); + const name_ty = Type.initTag(.const_slice_u8_sentinel_0); + const abi_size = name_ty.abiSize(self.target); + + const error_name_value: WValue = .{ .memory = error_table_symbol }; // emitting this will create a relocation + try self.emitWValue(error_name_value); + try self.emitWValue(operand); + switch (self.arch()) { + .wasm32 => { + try self.addImm32(@bitCast(i32, @intCast(u32, abi_size))); + try self.addTag(.i32_mul); + try self.addTag(.i32_add); + }, + .wasm64 => { + try self.addImm64(abi_size); + try self.addTag(.i64_mul); + try self.addTag(.i64_add); + }, + else => unreachable, + } + + const result_ptr = try self.allocLocal(Type.usize); + try self.addLabel(.local_set, result_ptr.local); + return result_ptr; +} diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 1288b27a81..2eb102c752 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -123,6 +123,13 @@ symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, *Atom) = .{}, /// Note: The value represents the offset into the string table, rather than the actual string. export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{}, +/// Represents the symbol index of the error name table +/// When this is `null`, no code references an error using runtime `@errorName`. +/// During initializion, a symbol with corresponding atom will be created that is +/// used to perform relocations to the pointer of this table. +/// The actual table is populated during `flush`. +error_table_symbol: ?u32 = null, + pub const Segment = struct { alignment: u32, size: u32, @@ -1322,6 +1329,123 @@ pub fn getMatchingSegment(self: *Wasm, object_index: u16, relocatable_index: u32 } } +/// Returns the symbol index of the error name table. +/// +/// When the symbol does not yet exist, it will create a new one instead. +pub fn getErrorTableSymbol(self: *Wasm) !u32 { + if (self.error_table_symbol) |symbol| { + return symbol; + } + + // no error was referenced yet, so create a new symbol and atom for it + // and then return said symbol's index. The final table will be populated + // during `flush` when we know all possible error names. + + // As sym_index '0' is reserved, we use it for our stack pointer symbol + const symbol_index = self.symbols_free_list.popOrNull() orelse blk: { + const index = @intCast(u32, self.symbols.items.len); + _ = try self.symbols.addOne(self.base.allocator); + break :blk index; + }; + + const sym_name = try self.string_table.put(self.base.allocator, "__zig_err_name_table"); + const symbol = &self.symbols.items[symbol_index]; + symbol.* = .{ + .name = sym_name, + .tag = .data, + .flags = 0, + .index = 0, + }; + symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); + + const slice_ty = Type.initTag(.const_slice_u8_sentinel_0); + + const atom = try self.base.allocator.create(Atom); + atom.* = Atom.empty; + atom.sym_index = symbol_index; + atom.alignment = slice_ty.abiAlignment(self.base.options.target); + try self.managed_atoms.append(self.base.allocator, atom); + const loc = atom.symbolLoc(); + try self.resolved_symbols.put(self.base.allocator, loc, {}); + try self.symbol_atom.put(self.base.allocator, loc, atom); + + log.debug("Error name table was created with symbol index: ({d})", .{symbol_index}); + self.error_table_symbol = symbol_index; + return symbol_index; +} + +/// Populates the error name table, when `error_table_symbol` is not null. +/// +/// This creates a table that consists of pointers and length to each error name. +/// The table is what is being pointed to within the runtime bodies that are generated. +fn populateErrorNameTable(self: *Wasm) !void { + const symbol_index = self.error_table_symbol orelse return; + const atom: *Atom = self.symbol_atom.get(.{ .file = null, .index = symbol_index }).?; + // Rather than creating a symbol for each individual error name, + // we create a symbol for the entire region of error names. We then calculate + // the pointers into the list using addends which are appended to the relocation. + const names_atom = try self.base.allocator.create(Atom); + names_atom.* = Atom.empty; + try self.managed_atoms.append(self.base.allocator, names_atom); + const names_symbol_index = self.symbols_free_list.popOrNull() orelse blk: { + const index = @intCast(u32, self.symbols.items.len); + _ = try self.symbols.addOne(self.base.allocator); + break :blk index; + }; + names_atom.sym_index = names_symbol_index; + names_atom.alignment = 1; + const sym_name = try self.string_table.put(self.base.allocator, "__zig_err_names"); + const names_symbol = &self.symbols.items[names_symbol_index]; + names_symbol.* = .{ + .name = sym_name, + .tag = .data, + .flags = 0, + .index = 0, + }; + names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); + + log.debug("Populating error names", .{}); + + // Addend for each relocation to the table + var addend: u32 = 0; + const module = self.base.options.module.?; + for (module.error_name_list.items) |error_name| { + const len = @intCast(u32, error_name.len + 1); // names are 0-termianted + + const slice_ty = Type.initTag(.const_slice_u8_sentinel_0); + const offset = @intCast(u32, atom.code.items.len); + // first we create the data for the slice of the name + try atom.code.appendNTimes(self.base.allocator, 0, 4); // ptr to name, will be relocated + try atom.code.writer(self.base.allocator).writeIntLittle(u32, len - 1); + // create relocation to the error name + try atom.relocs.append(self.base.allocator, .{ + .index = names_symbol_index, + .relocation_type = .R_WASM_MEMORY_ADDR_I32, + .offset = offset, + .addend = addend, + }); + atom.size += @intCast(u32, slice_ty.abiSize(self.base.options.target)); + addend += len; + + // as we updated the error name table, we now store the actual name within the names atom + try names_atom.code.ensureUnusedCapacity(self.base.allocator, len); + names_atom.code.appendSliceAssumeCapacity(error_name); + names_atom.code.appendAssumeCapacity(0); + + log.debug("Populated error name: '{s}'", .{error_name}); + } + names_atom.size = addend; + + const name_loc = names_atom.symbolLoc(); + try self.resolved_symbols.put(self.base.allocator, name_loc, {}); + try self.symbol_atom.put(self.base.allocator, name_loc, names_atom); + + // link the atoms with the rest of the binary so they can be allocated + // and relocations will be performed. + try self.parseAtom(atom, .data); + try self.parseAtom(names_atom, .data); +} + fn resetState(self: *Wasm) void { for (self.segment_info.items) |*segment_info| { self.base.allocator.free(segment_info.name); @@ -1373,6 +1497,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { } } + // ensure the error names table is populated when an error name is referenced + try self.populateErrorNameTable(); + // The amount of sections that will be written var section_count: u32 = 0; // Index of the code section. Used to tell relocation table where the section lives. -- cgit v1.2.3 From a9a629f89a1d72130103bd59800e1257af84c97c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 22 Mar 2022 21:56:38 +0100 Subject: wasm: Fix switching on errors Error sets contain the entire global error set. Users are often switching on specific errors only present within that operand. This means that cases are almost always sparse and not contiguous. For this reason, we will instead emit the default case for error values not present in that specific operand error set. This is fine as those cases will never be hit, as prevented by the type system. By still allowing jump tables for those cases, rather than if-else chains, we save runtime cost as well as binary size. --- src/arch/wasm/CodeGen.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 136ef5cc45..0300c0b612 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -2452,7 +2452,11 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { if (case_value.integer == value) break :blk @intCast(u32, idx); } } - break :blk if (has_else_body) case_i else unreachable; + // error sets are almost always sparse so we use the default case + // for errors that are not present in any branch. This is fine as this default + // case will never be hit for those cases but we do save runtime cost and size + // by using a jump table for this instead of if-else chains. + break :blk if (has_else_body or target_ty.zigTypeTag() == .ErrorSet) case_i else unreachable; }; self.mir_extra.appendAssumeCapacity(idx); } else if (has_else_body) { -- cgit v1.2.3 From 685f05b562b1e5d001ae2da23485c03b704d19f6 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 23 Mar 2022 18:33:48 +0100 Subject: wasm: Implement opt_payload_ptr Implements lowering constants for pointers of value 'opt_payload_ptr'. The offset is calculated by determining the abi size of the full type and then substracting the payload size. --- src/arch/wasm/CodeGen.zig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 0300c0b612..0415d8c90a 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1886,6 +1886,23 @@ fn lowerParentPtr(self: *Self, ptr_val: Value, ptr_child_ty: Type) InnerError!WV .offset = @intCast(u32, offset), } }; }, + .opt_payload_ptr => { + const payload_ptr = ptr_val.castTag(.opt_payload_ptr).?.data; + const parent_ptr = try self.lowerParentPtr(payload_ptr.container_ptr, payload_ptr.container_ty); + var buf: Type.Payload.ElemType = undefined; + const payload_ty = payload_ptr.container_ty.optionalChild(&buf); + if (!payload_ty.hasRuntimeBitsIgnoreComptime() or payload_ty.isPtrLikeOptional()) { + return parent_ptr; + } + + const abi_size = payload_ptr.container_ty.abiSize(self.target); + const offset = abi_size - payload_ty.abiSize(self.target); + + return WValue{ .memory_offset = .{ + .pointer = parent_ptr.memory, + .offset = @intCast(u32, offset), + } }; + }, else => |tag| return self.fail("TODO: Implement lowerParentPtr for tag: {}", .{tag}), } } @@ -1948,7 +1965,7 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue { else => unreachable, }, .Pointer => switch (val.tag()) { - .field_ptr, .elem_ptr => { + .field_ptr, .elem_ptr, .opt_payload_ptr => { return self.lowerParentPtr(val, ty.childType()); }, .int_u64, .one => return WValue{ .imm32 = @intCast(u32, val.toUnsignedInt(target)) }, -- cgit v1.2.3 From 60676a0ba50ece8363883bdbaa803f31d8284c8c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 23 Mar 2022 20:42:11 +0100 Subject: wasm: Implement more instructions Implements the following instructions: - int_to_float - ptr_slice_len_ptr - ptr_slice_ptr_ptr - unwrap_errunion_payload_ptr - unwrap_errunion_err_ptr --- src/arch/wasm/CodeGen.zig | 58 ++++++++++++++++++++++++++++++++++++----------- src/arch/wasm/Emit.zig | 8 +++++++ src/arch/wasm/Mir.zig | 16 +++++++++++++ 3 files changed, 69 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 0415d8c90a..eb8d72a994 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1329,6 +1329,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .fptrunc => self.airFptrunc(inst), .fpext => self.airFpext(inst), .float_to_int => self.airFloatToInt(inst), + .int_to_float => self.airIntToFloat(inst), .get_union_tag => self.airGetUnionTag(inst), // TODO @@ -1382,6 +1383,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .slice_elem_val => self.airSliceElemVal(inst), .slice_elem_ptr => self.airSliceElemPtr(inst), .slice_ptr => self.airSlicePtr(inst), + .ptr_slice_len_ptr => self.airPtrSliceFieldPtr(inst, self.ptrSize()), + .ptr_slice_ptr_ptr => self.airPtrSliceFieldPtr(inst, 0), .store => self.airStore(inst), .set_union_tag => self.airSetUnionTag(inst), @@ -1398,8 +1401,10 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .unreach => self.airUnreachable(inst), .wrap_optional => self.airWrapOptional(inst), - .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst), - .unwrap_errunion_err => self.airUnwrapErrUnionError(inst), + .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst, false), + .unwrap_errunion_payload_ptr => self.airUnwrapErrUnionPayload(inst, true), + .unwrap_errunion_err => self.airUnwrapErrUnionError(inst, false), + .unwrap_errunion_err_ptr => self.airUnwrapErrUnionError(inst, true), .wrap_errunion_payload => self.airWrapErrUnionPayload(inst), .wrap_errunion_err => self.airWrapErrUnionErr(inst), .errunion_payload_ptr_set => self.airErrUnionPayloadPtrSet(inst), @@ -1429,8 +1434,6 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .bit_reverse, .is_err_ptr, .is_non_err_ptr, - .unwrap_errunion_payload_ptr, - .unwrap_errunion_err_ptr, .sqrt, .sin, @@ -1446,9 +1449,6 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .round, .trunc_float, - .ptr_slice_len_ptr, - .ptr_slice_ptr_ptr, - .int_to_float, .cmpxchg_weak, .cmpxchg_strong, .fence, @@ -2560,30 +2560,32 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!W return is_err_tmp; } -fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { +fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index, op_is_ptr: bool) InnerError!WValue { if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand = try self.resolveInst(ty_op.operand); - const err_ty = self.air.typeOf(ty_op.operand); + const op_ty = self.air.typeOf(ty_op.operand); + const err_ty = if (op_is_ptr) op_ty.childType() else op_ty; const payload_ty = err_ty.errorUnionPayload(); if (!payload_ty.hasRuntimeBits()) return WValue{ .none = {} }; const err_align = err_ty.abiAlignment(self.target); const set_size = err_ty.errorUnionSet().abiSize(self.target); const offset = mem.alignForwardGeneric(u64, set_size, err_align); - if (isByRef(payload_ty, self.target)) { + if (op_is_ptr or isByRef(payload_ty, self.target)) { return self.buildPointerOffset(operand, offset, .new); } return self.load(operand, payload_ty, @intCast(u32, offset)); } -fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue { +fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index, op_is_ptr: bool) InnerError!WValue { if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand = try self.resolveInst(ty_op.operand); - const err_ty = self.air.typeOf(ty_op.operand); + const op_ty = self.air.typeOf(ty_op.operand); + const err_ty = if (op_is_ptr) op_ty.childType() else op_ty; const payload_ty = err_ty.errorUnionPayload(); - if (!payload_ty.hasRuntimeBits()) { + if (op_is_ptr or !payload_ty.hasRuntimeBits()) { return operand; } @@ -3231,6 +3233,28 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { return result; } +fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_ty = self.air.typeOfIndex(inst); + const op_ty = self.air.typeOf(ty_op.operand); + + try self.emitWValue(operand); + const op = buildOpcode(.{ + .op = .convert, + .valtype1 = typeToValtype(dest_ty, self.target), + .valtype2 = typeToValtype(op_ty, self.target), + .signedness = if (op_ty.isSignedInt()) .signed else .unsigned, + }); + try self.addTag(Mir.Inst.Tag.fromOpcode(op)); + + const result = try self.allocLocal(dest_ty); + try self.addLabel(.local_set, result.local); + return result; +} + fn airSplat(self: *Self, inst: Air.Inst.Index) InnerError!WValue { if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; @@ -3682,3 +3706,11 @@ fn airErrorName(self: *Self, inst: Air.Inst.Index) InnerError!WValue { try self.addLabel(.local_set, result_ptr.local); return result_ptr; } + +fn airPtrSliceFieldPtr(self: *Self, inst: Air.Inst.Index, offset: u32) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const slice_ptr = try self.resolveInst(ty_op.operand); + return self.buildPointerOffset(slice_ptr, offset, .new); +} diff --git a/src/arch/wasm/Emit.zig b/src/arch/wasm/Emit.zig index f0f4cfae5d..7487b014be 100644 --- a/src/arch/wasm/Emit.zig +++ b/src/arch/wasm/Emit.zig @@ -203,6 +203,14 @@ pub fn emitMir(emit: *Emit) InnerError!void { .i64_trunc_f32_u => try emit.emitTag(tag), .i64_trunc_f64_s => try emit.emitTag(tag), .i64_trunc_f64_u => try emit.emitTag(tag), + .f32_convert_i32_s => try emit.emitTag(tag), + .f32_convert_i32_u => try emit.emitTag(tag), + .f32_convert_i64_s => try emit.emitTag(tag), + .f32_convert_i64_u => try emit.emitTag(tag), + .f64_convert_i32_s => try emit.emitTag(tag), + .f64_convert_i32_u => try emit.emitTag(tag), + .f64_convert_i64_s => try emit.emitTag(tag), + .f64_convert_i64_u => try emit.emitTag(tag), .i32_rem_s => try emit.emitTag(tag), .i32_rem_u => try emit.emitTag(tag), .i64_rem_s => try emit.emitTag(tag), diff --git a/src/arch/wasm/Mir.zig b/src/arch/wasm/Mir.zig index 09a0c32901..395e9bb17c 100644 --- a/src/arch/wasm/Mir.zig +++ b/src/arch/wasm/Mir.zig @@ -451,8 +451,24 @@ pub const Inst = struct { /// Uses `tag` i64_trunc_f64_u = 0xB1, /// Uses `tag` + f32_convert_i32_s = 0xB2, + /// Uses `tag` + f32_convert_i32_u = 0xB3, + /// Uses `tag` + f32_convert_i64_s = 0xB4, + /// Uses `tag` + f32_convert_i64_u = 0xB5, + /// Uses `tag` f32_demote_f64 = 0xB6, /// Uses `tag` + f64_convert_i32_s = 0xB7, + /// Uses `tag` + f64_convert_i32_u = 0xB8, + /// Uses `tag` + f64_convert_i64_s = 0xB9, + /// Uses `tag` + f64_convert_i64_u = 0xBA, + /// Uses `tag` f64_promote_f32 = 0xBB, /// Uses `tag` i32_reinterpret_f32 = 0xBC, -- cgit v1.2.3