authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-03-22 21:20:36+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-03-23 21:40:32+01:00
log49051c065120781b6ff78172c447b6307bd01e39
tree37efe02f2da2419f37fec7ce152313229cab6950
parentb872539a13ac46abe57a59bafdf5392812468482
signaturelock-open Commit is signed but in an unrecognized format.

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.

2 files changed, 171 insertions(+), 1 deletions(-)

src/arch/wasm/CodeGen.zig+44-1
...@@ -1403,6 +1403,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1403,6 +1403,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1403 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),1403 .wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
1404 .wrap_errunion_err => self.airWrapErrUnionErr(inst),1404 .wrap_errunion_err => self.airWrapErrUnionErr(inst),
1405 .errunion_payload_ptr_set => self.airErrUnionPayloadPtrSet(inst),1405 .errunion_payload_ptr_set => self.airErrUnionPayloadPtrSet(inst),
1406 .error_name => self.airErrorName(inst),
14061407
1407 .wasm_memory_size => self.airWasmMemorySize(inst),1408 .wasm_memory_size => self.airWasmMemorySize(inst),
1408 .wasm_memory_grow => self.airWasmMemoryGrow(inst),1409 .wasm_memory_grow => self.airWasmMemoryGrow(inst),
...@@ -1458,7 +1459,6 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {...@@ -1458,7 +1459,6 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1458 .atomic_store_seq_cst,1459 .atomic_store_seq_cst,
1459 .atomic_rmw,1460 .atomic_rmw,
1460 .tag_name,1461 .tag_name,
1461 .error_name,
1462 .mul_add,1462 .mul_add,
14631463
1464 // For these 4, probably best to wait until https://github.com/ziglang/zig/issues/102481464 // 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 {...@@ -3618,3 +3618,46 @@ fn airPopcount(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3618 try self.addLabel(.local_set, result.local);3618 try self.addLabel(.local_set, result.local);
3619 return result;3619 return result;
3620}3620}
3621
3622fn airErrorName(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3623 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
3624
3625 const un_op = self.air.instructions.items(.data)[inst].un_op;
3626 const operand = try self.resolveInst(un_op);
3627
3628 // First retrieve the symbol index to the error name table
3629 // that will be used to emit a relocation for the pointer
3630 // to the error name table.
3631 //
3632 // Each entry to this table is a slice (ptr+len).
3633 // The operand in this instruction represents the index within this table.
3634 // This means to get the final name, we emit the base pointer and then perform
3635 // pointer arithmetic to find the pointer to this slice and return that.
3636 //
3637 // As the names are global and the slice elements are constant, we do not have
3638 // to make a copy of the ptr+value but can point towards them directly.
3639 const error_table_symbol = try self.bin_file.getErrorTableSymbol();
3640 const name_ty = Type.initTag(.const_slice_u8_sentinel_0);
3641 const abi_size = name_ty.abiSize(self.target);
3642
3643 const error_name_value: WValue = .{ .memory = error_table_symbol }; // emitting this will create a relocation
3644 try self.emitWValue(error_name_value);
3645 try self.emitWValue(operand);
3646 switch (self.arch()) {
3647 .wasm32 => {
3648 try self.addImm32(@bitCast(i32, @intCast(u32, abi_size)));
3649 try self.addTag(.i32_mul);
3650 try self.addTag(.i32_add);
3651 },
3652 .wasm64 => {
3653 try self.addImm64(abi_size);
3654 try self.addTag(.i64_mul);
3655 try self.addTag(.i64_add);
3656 },
3657 else => unreachable,
3658 }
3659
3660 const result_ptr = try self.allocLocal(Type.usize);
3661 try self.addLabel(.local_set, result_ptr.local);
3662 return result_ptr;
3663}
src/link/Wasm.zig+127
...@@ -123,6 +123,13 @@ symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, *Atom) = .{},...@@ -123,6 +123,13 @@ symbol_atom: std.AutoHashMapUnmanaged(SymbolLoc, *Atom) = .{},
123/// Note: The value represents the offset into the string table, rather than the actual string.123/// Note: The value represents the offset into the string table, rather than the actual string.
124export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{},124export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{},
125125
126/// Represents the symbol index of the error name table
127/// When this is `null`, no code references an error using runtime `@errorName`.
128/// During initializion, a symbol with corresponding atom will be created that is
129/// used to perform relocations to the pointer of this table.
130/// The actual table is populated during `flush`.
131error_table_symbol: ?u32 = null,
132
126pub const Segment = struct {133pub const Segment = struct {
127 alignment: u32,134 alignment: u32,
128 size: u32,135 size: u32,
...@@ -1322,6 +1329,123 @@ pub fn getMatchingSegment(self: *Wasm, object_index: u16, relocatable_index: u32...@@ -1322,6 +1329,123 @@ pub fn getMatchingSegment(self: *Wasm, object_index: u16, relocatable_index: u32
1322 }1329 }
1323}1330}
13241331
1332/// Returns the symbol index of the error name table.
1333///
1334/// When the symbol does not yet exist, it will create a new one instead.
1335pub fn getErrorTableSymbol(self: *Wasm) !u32 {
1336 if (self.error_table_symbol) |symbol| {
1337 return symbol;
1338 }
1339
1340 // no error was referenced yet, so create a new symbol and atom for it
1341 // and then return said symbol's index. The final table will be populated
1342 // during `flush` when we know all possible error names.
1343
1344 // As sym_index '0' is reserved, we use it for our stack pointer symbol
1345 const symbol_index = self.symbols_free_list.popOrNull() orelse blk: {
1346 const index = @intCast(u32, self.symbols.items.len);
1347 _ = try self.symbols.addOne(self.base.allocator);
1348 break :blk index;
1349 };
1350
1351 const sym_name = try self.string_table.put(self.base.allocator, "__zig_err_name_table");
1352 const symbol = &self.symbols.items[symbol_index];
1353 symbol.* = .{
1354 .name = sym_name,
1355 .tag = .data,
1356 .flags = 0,
1357 .index = 0,
1358 };
1359 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
1360
1361 const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
1362
1363 const atom = try self.base.allocator.create(Atom);
1364 atom.* = Atom.empty;
1365 atom.sym_index = symbol_index;
1366 atom.alignment = slice_ty.abiAlignment(self.base.options.target);
1367 try self.managed_atoms.append(self.base.allocator, atom);
1368 const loc = atom.symbolLoc();
1369 try self.resolved_symbols.put(self.base.allocator, loc, {});
1370 try self.symbol_atom.put(self.base.allocator, loc, atom);
1371
1372 log.debug("Error name table was created with symbol index: ({d})", .{symbol_index});
1373 self.error_table_symbol = symbol_index;
1374 return symbol_index;
1375}
1376
1377/// Populates the error name table, when `error_table_symbol` is not null.
1378///
1379/// This creates a table that consists of pointers and length to each error name.
1380/// The table is what is being pointed to within the runtime bodies that are generated.
1381fn populateErrorNameTable(self: *Wasm) !void {
1382 const symbol_index = self.error_table_symbol orelse return;
1383 const atom: *Atom = self.symbol_atom.get(.{ .file = null, .index = symbol_index }).?;
1384 // Rather than creating a symbol for each individual error name,
1385 // we create a symbol for the entire region of error names. We then calculate
1386 // the pointers into the list using addends which are appended to the relocation.
1387 const names_atom = try self.base.allocator.create(Atom);
1388 names_atom.* = Atom.empty;
1389 try self.managed_atoms.append(self.base.allocator, names_atom);
1390 const names_symbol_index = self.symbols_free_list.popOrNull() orelse blk: {
1391 const index = @intCast(u32, self.symbols.items.len);
1392 _ = try self.symbols.addOne(self.base.allocator);
1393 break :blk index;
1394 };
1395 names_atom.sym_index = names_symbol_index;
1396 names_atom.alignment = 1;
1397 const sym_name = try self.string_table.put(self.base.allocator, "__zig_err_names");
1398 const names_symbol = &self.symbols.items[names_symbol_index];
1399 names_symbol.* = .{
1400 .name = sym_name,
1401 .tag = .data,
1402 .flags = 0,
1403 .index = 0,
1404 };
1405 names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
1406
1407 log.debug("Populating error names", .{});
1408
1409 // Addend for each relocation to the table
1410 var addend: u32 = 0;
1411 const module = self.base.options.module.?;
1412 for (module.error_name_list.items) |error_name| {
1413 const len = @intCast(u32, error_name.len + 1); // names are 0-termianted
1414
1415 const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
1416 const offset = @intCast(u32, atom.code.items.len);
1417 // first we create the data for the slice of the name
1418 try atom.code.appendNTimes(self.base.allocator, 0, 4); // ptr to name, will be relocated
1419 try atom.code.writer(self.base.allocator).writeIntLittle(u32, len - 1);
1420 // create relocation to the error name
1421 try atom.relocs.append(self.base.allocator, .{
1422 .index = names_symbol_index,
1423 .relocation_type = .R_WASM_MEMORY_ADDR_I32,
1424 .offset = offset,
1425 .addend = addend,
1426 });
1427 atom.size += @intCast(u32, slice_ty.abiSize(self.base.options.target));
1428 addend += len;
1429
1430 // as we updated the error name table, we now store the actual name within the names atom
1431 try names_atom.code.ensureUnusedCapacity(self.base.allocator, len);
1432 names_atom.code.appendSliceAssumeCapacity(error_name);
1433 names_atom.code.appendAssumeCapacity(0);
1434
1435 log.debug("Populated error name: '{s}'", .{error_name});
1436 }
1437 names_atom.size = addend;
1438
1439 const name_loc = names_atom.symbolLoc();
1440 try self.resolved_symbols.put(self.base.allocator, name_loc, {});
1441 try self.symbol_atom.put(self.base.allocator, name_loc, names_atom);
1442
1443 // link the atoms with the rest of the binary so they can be allocated
1444 // and relocations will be performed.
1445 try self.parseAtom(atom, .data);
1446 try self.parseAtom(names_atom, .data);
1447}
1448
1325fn resetState(self: *Wasm) void {1449fn resetState(self: *Wasm) void {
1326 for (self.segment_info.items) |*segment_info| {1450 for (self.segment_info.items) |*segment_info| {
1327 self.base.allocator.free(segment_info.name);1451 self.base.allocator.free(segment_info.name);
...@@ -1373,6 +1497,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -1373,6 +1497,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
1373 }1497 }
1374 }1498 }
13751499
1500 // ensure the error names table is populated when an error name is referenced
1501 try self.populateErrorNameTable();
1502
1376 // The amount of sections that will be written1503 // The amount of sections that will be written
1377 var section_count: u32 = 0;1504 var section_count: u32 = 0;
1378 // Index of the code section. Used to tell relocation table where the section lives.1505 // Index of the code section. Used to tell relocation table where the section lives.