Commit 49051c0651
Changed files (2)
src
arch
wasm
link
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;
+}
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.