authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-04-13 19:32:22+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-04-13 19:32:22+02:00
log25e3851fe0f7fe254b48c2242ab8d4ef89165fd5
treec7991ec10945496de6dcb8f8aae32d12c077ecd6
parentfbcf1c0006dc6656deb3f04e2f52d1da4a599952
parentabc1e52e0885c8e46d743d5e48ba260b35ae2769
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15257 from Luukdegram/wasm-tagname

wasm: Implement `@tagName` instruction

4 files changed, 248 insertions(+), 7 deletions(-)

src/arch/wasm/CodeGen.zig+192-1
......@@ -1944,6 +1944,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19441944 .memcpy => func.airMemcpy(inst),
19451945
19461946 .ret_addr => func.airRetAddr(inst),
1947 .tag_name => func.airTagName(inst),
19471948
19481949 .mul_sat,
19491950 .mod,
......@@ -1962,7 +1963,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
19621963 .atomic_store_release,
19631964 .atomic_store_seq_cst,
19641965 .atomic_rmw,
1965 .tag_name,
19661966 .err_return_trace,
19671967 .set_err_return_trace,
19681968 .save_err_return_trace_index,
......@@ -6396,3 +6396,194 @@ fn callIntrinsic(
63966396 return WValue{ .stack = {} };
63976397 }
63986398}
6399
6400fn airTagName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
6401 const un_op = func.air.instructions.items(.data)[inst].un_op;
6402 if (func.liveness.isUnused(inst)) return func.finishAir(inst, .none, &.{un_op});
6403 const operand = try func.resolveInst(un_op);
6404 const enum_ty = func.air.typeOf(un_op);
6405
6406 const func_sym_index = try func.getTagNameFunction(enum_ty);
6407
6408 const result_ptr = try func.allocStack(func.air.typeOfIndex(inst));
6409 try func.lowerToStack(result_ptr);
6410 try func.emitWValue(operand);
6411 try func.addLabel(.call, func_sym_index);
6412
6413 return func.finishAir(inst, result_ptr, &.{un_op});
6414}
6415
6416fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
6417 const enum_decl_index = enum_ty.getOwnerDecl();
6418 const module = func.bin_file.base.options.module.?;
6419
6420 var arena_allocator = std.heap.ArenaAllocator.init(func.gpa);
6421 defer arena_allocator.deinit();
6422 const arena = arena_allocator.allocator();
6423
6424 const fqn = try module.declPtr(enum_decl_index).getFullyQualifiedName(module);
6425 defer module.gpa.free(fqn);
6426 const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn});
6427
6428 // check if we already generated code for this.
6429 if (func.bin_file.findGlobalSymbol(func_name)) |loc| {
6430 return loc.index;
6431 }
6432
6433 var int_tag_type_buffer: Type.Payload.Bits = undefined;
6434 const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
6435
6436 if (int_tag_ty.bitSize(func.target) > 64) {
6437 return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{});
6438 }
6439
6440 var relocs = std.ArrayList(link.File.Wasm.Relocation).init(func.gpa);
6441 defer relocs.deinit();
6442
6443 var body_list = std.ArrayList(u8).init(func.gpa);
6444 defer body_list.deinit();
6445 var writer = body_list.writer();
6446
6447 // The locals of the function body (always 0)
6448 try leb.writeULEB128(writer, @as(u32, 0));
6449
6450 // outer block
6451 try writer.writeByte(std.wasm.opcode(.block));
6452 try writer.writeByte(std.wasm.block_empty);
6453
6454 // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse.
6455 // generate an if-else chain for each tag value as well as constant.
6456 for (enum_ty.enumFields().keys(), 0..) |tag_name, field_index| {
6457 // for each tag name, create an unnamed const,
6458 // and then get a pointer to its value.
6459 var name_ty_payload: Type.Payload.Len = .{
6460 .base = .{ .tag = .array_u8_sentinel_0 },
6461 .data = @intCast(u64, tag_name.len),
6462 };
6463 const name_ty = Type.initPayload(&name_ty_payload.base);
6464 const string_bytes = &module.string_literal_bytes;
6465 try string_bytes.ensureUnusedCapacity(module.gpa, tag_name.len);
6466 const gop = try module.string_literal_table.getOrPutContextAdapted(module.gpa, tag_name, Module.StringLiteralAdapter{
6467 .bytes = string_bytes,
6468 }, Module.StringLiteralContext{
6469 .bytes = string_bytes,
6470 });
6471 if (!gop.found_existing) {
6472 gop.key_ptr.* = .{
6473 .index = @intCast(u32, string_bytes.items.len),
6474 .len = @intCast(u32, tag_name.len),
6475 };
6476 string_bytes.appendSliceAssumeCapacity(tag_name);
6477 gop.value_ptr.* = .none;
6478 }
6479 var name_val_payload: Value.Payload.StrLit = .{
6480 .base = .{ .tag = .str_lit },
6481 .data = gop.key_ptr.*,
6482 };
6483 const name_val = Value.initPayload(&name_val_payload.base);
6484 const tag_sym_index = try func.bin_file.lowerUnnamedConst(
6485 .{ .ty = name_ty, .val = name_val },
6486 enum_decl_index,
6487 );
6488
6489 // block for this if case
6490 try writer.writeByte(std.wasm.opcode(.block));
6491 try writer.writeByte(std.wasm.block_empty);
6492
6493 // get actual tag value (stored in 2nd parameter);
6494 try writer.writeByte(std.wasm.opcode(.local_get));
6495 try leb.writeULEB128(writer, @as(u32, 1));
6496
6497 var tag_val_payload: Value.Payload.U32 = .{
6498 .base = .{ .tag = .enum_field_index },
6499 .data = @intCast(u32, field_index),
6500 };
6501 const tag_value = try func.lowerConstant(Value.initPayload(&tag_val_payload.base), enum_ty);
6502
6503 switch (tag_value) {
6504 .imm32 => |value| {
6505 try writer.writeByte(std.wasm.opcode(.i32_const));
6506 try leb.writeULEB128(writer, value);
6507 try writer.writeByte(std.wasm.opcode(.i32_ne));
6508 },
6509 .imm64 => |value| {
6510 try writer.writeByte(std.wasm.opcode(.i64_const));
6511 try leb.writeULEB128(writer, value);
6512 try writer.writeByte(std.wasm.opcode(.i64_ne));
6513 },
6514 else => unreachable,
6515 }
6516 // if they're not equal, break out of current branch
6517 try writer.writeByte(std.wasm.opcode(.br_if));
6518 try leb.writeULEB128(writer, @as(u32, 0));
6519
6520 // store the address of the tagname in the pointer field of the slice
6521 // get the address twice so we can also store the length.
6522 try writer.writeByte(std.wasm.opcode(.local_get));
6523 try leb.writeULEB128(writer, @as(u32, 0));
6524 try writer.writeByte(std.wasm.opcode(.local_get));
6525 try leb.writeULEB128(writer, @as(u32, 0));
6526
6527 // get address of tagname and emit a relocation to it
6528 if (func.arch() == .wasm32) {
6529 const encoded_alignment = @ctz(@as(u32, 4));
6530 try writer.writeByte(std.wasm.opcode(.i32_const));
6531 try relocs.append(.{
6532 .relocation_type = .R_WASM_MEMORY_ADDR_LEB,
6533 .offset = @intCast(u32, body_list.items.len),
6534 .index = tag_sym_index,
6535 });
6536 try writer.writeAll(&[_]u8{0} ** 5); // will be relocated
6537
6538 // store pointer
6539 try writer.writeByte(std.wasm.opcode(.i32_store));
6540 try leb.writeULEB128(writer, encoded_alignment);
6541 try leb.writeULEB128(writer, @as(u32, 0));
6542
6543 // store length
6544 try writer.writeByte(std.wasm.opcode(.i32_const));
6545 try leb.writeULEB128(writer, @intCast(u32, tag_name.len));
6546 try writer.writeByte(std.wasm.opcode(.i32_store));
6547 try leb.writeULEB128(writer, encoded_alignment);
6548 try leb.writeULEB128(writer, @as(u32, 4));
6549 } else {
6550 const encoded_alignment = @ctz(@as(u32, 8));
6551 try writer.writeByte(std.wasm.opcode(.i64_const));
6552 try relocs.append(.{
6553 .relocation_type = .R_WASM_MEMORY_ADDR_LEB64,
6554 .offset = @intCast(u32, body_list.items.len),
6555 .index = tag_sym_index,
6556 });
6557 try writer.writeAll(&[_]u8{0} ** 10); // will be relocated
6558
6559 // store pointer
6560 try writer.writeByte(std.wasm.opcode(.i64_store));
6561 try leb.writeULEB128(writer, encoded_alignment);
6562 try leb.writeULEB128(writer, @as(u32, 0));
6563
6564 // store length
6565 try writer.writeByte(std.wasm.opcode(.i64_const));
6566 try leb.writeULEB128(writer, @intCast(u64, tag_name.len));
6567 try writer.writeByte(std.wasm.opcode(.i64_store));
6568 try leb.writeULEB128(writer, encoded_alignment);
6569 try leb.writeULEB128(writer, @as(u32, 8));
6570 }
6571
6572 // break outside blocks
6573 try writer.writeByte(std.wasm.opcode(.br));
6574 try leb.writeULEB128(writer, @as(u32, 1));
6575
6576 // end the block for this case
6577 try writer.writeByte(std.wasm.opcode(.end));
6578 }
6579
6580 try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name
6581 // finish outer block
6582 try writer.writeByte(std.wasm.opcode(.end));
6583 // finish function body
6584 try writer.writeByte(std.wasm.opcode(.end));
6585
6586 const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
6587 const func_type = try genFunctype(arena, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);
6588 return func.bin_file.createFunction(func_name, func_type, &body_list, &relocs);
6589}
src/link/Wasm.zig+55-1
......@@ -30,6 +30,7 @@ const Symbol = @import("Wasm/Symbol.zig");
3030const Object = @import("Wasm/Object.zig");
3131const Archive = @import("Wasm/Archive.zig");
3232const types = @import("Wasm/types.zig");
33pub const Relocation = types.Relocation;
3334
3435pub const base_tag: link.File.Tag = .wasm;
3536
......@@ -178,6 +179,10 @@ debug_str_atom: ?Atom.Index = null,
178179debug_pubnames_atom: ?Atom.Index = null,
179180debug_pubtypes_atom: ?Atom.Index = null,
180181
182/// List of atom indexes of functions that are generated by the backend,
183/// rather than by the linker.
184synthetic_functions: std.ArrayListUnmanaged(Atom.Index) = .{},
185
181186pub const Segment = struct {
182187 alignment: u32,
183188 size: u32,
......@@ -1208,7 +1213,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
12081213
12091214// Tries to find a global symbol by its name. Returns null when not found,
12101215/// and its location when it is found.
1211fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
1216pub fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
12121217 const offset = wasm.string_table.getOffset(name) orelse return null;
12131218 return wasm.globals.get(offset);
12141219}
......@@ -1287,6 +1292,7 @@ pub fn deinit(wasm: *Wasm) void {
12871292 wasm.exports.deinit(gpa);
12881293
12891294 wasm.string_table.deinit(gpa);
1295 wasm.synthetic_functions.deinit(gpa);
12901296
12911297 if (wasm.dwarf) |*dwarf| {
12921298 dwarf.deinit();
......@@ -2272,6 +2278,49 @@ fn createSyntheticFunction(
22722278 atom.offset = prev_atom.offset + prev_atom.size;
22732279}
22742280
2281/// Unlike `createSyntheticFunction` this function is to be called by
2282/// the codegeneration backend. This will not allocate the created Atom yet,
2283/// but will instead be appended to `synthetic_functions` list and will be
2284/// parsed at the end of code generation.
2285/// Returns the index of the symbol.
2286pub fn createFunction(
2287 wasm: *Wasm,
2288 symbol_name: []const u8,
2289 func_ty: std.wasm.Type,
2290 function_body: *std.ArrayList(u8),
2291 relocations: *std.ArrayList(Relocation),
2292) !u32 {
2293 const loc = try wasm.createSyntheticSymbol(symbol_name, .function);
2294
2295 const atom_index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
2296 const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
2297 atom.* = .{
2298 .size = @intCast(u32, function_body.items.len),
2299 .offset = 0,
2300 .sym_index = loc.index,
2301 .file = null,
2302 .alignment = 1,
2303 .next = null,
2304 .prev = null,
2305 .code = function_body.moveToUnmanaged(),
2306 .relocs = relocations.moveToUnmanaged(),
2307 };
2308 const symbol = loc.getSymbol(wasm);
2309 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); // ensure function does not get exported
2310
2311 const section_index = wasm.code_section_index orelse idx: {
2312 const index = @intCast(u32, wasm.segments.items.len);
2313 try wasm.appendDummySegment();
2314 break :idx index;
2315 };
2316 try wasm.appendAtomAtIndex(section_index, atom_index);
2317 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index);
2318 try wasm.atom_types.put(wasm.base.allocator, atom_index, try wasm.putOrGetFuncType(func_ty));
2319 try wasm.synthetic_functions.append(wasm.base.allocator, atom_index);
2320
2321 return loc.index;
2322}
2323
22752324fn initializeTLSFunction(wasm: *Wasm) !void {
22762325 if (!wasm.base.options.shared_memory) return;
22772326
......@@ -3306,6 +3355,11 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
33063355 }
33073356 }
33083357
3358 // also parse any backend-generated functions
3359 for (wasm.synthetic_functions.items) |atom_index| {
3360 try wasm.parseAtom(atom_index, .function);
3361 }
3362
33093363 if (wasm.dwarf) |*dwarf| {
33103364 try dwarf.flushModule(wasm.base.options.module.?);
33113365 }
src/link/Wasm/Atom.zig+1-1
......@@ -43,7 +43,7 @@ pub const Index = u32;
4343
4444/// Represents a default empty wasm `Atom`
4545pub const empty: Atom = .{
46 .alignment = 0,
46 .alignment = 1,
4747 .file = null,
4848 .next = null,
4949 .offset = 0,
test/behavior/enum.zig-4
......@@ -979,7 +979,6 @@ fn test3_2(f: Test3Foo) !void {
979979}
980980
981981test "@tagName" {
982 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
983982 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
984983 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
985984 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
......@@ -996,7 +995,6 @@ fn testEnumTagNameBare(n: anytype) []const u8 {
996995const BareNumber = enum { One, Two, Three };
997996
998997test "@tagName non-exhaustive enum" {
999 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1000998 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
1001999 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
10021000 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
......@@ -1008,7 +1006,6 @@ test "@tagName non-exhaustive enum" {
10081006const NonExhaustive = enum(u8) { A, B, _ };
10091007
10101008test "@tagName is null-terminated" {
1011 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
10121009 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
10131010 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
10141011 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
......@@ -1024,7 +1021,6 @@ test "@tagName is null-terminated" {
10241021}
10251022
10261023test "tag name with assigned enum values" {
1027 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
10281024 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
10291025 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
10301026 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;