authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-04-09 19:24:24+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-04-12 22:23:32+02:00
log3c27df6b135f998facce1aa09a9926e840671a05
tree4b400276a9df4f27bf574f671b2376dc3f8362cc
parent490a411fd4e00fee65bc014fa7b1af59042d269b
signature Commit is signed but in an unrecognized format.

wasm: generate function to get tag name

When we lower the instruction for `@tagName` we generate a new function if it doesn't exist yet for that decl. This function creates an if-else chain to determine which value was provided. Each tag generates a constant that contains its name as the value. For each tag we generate a case where the pointer of the string is stored in the result slice. The length of the tagname is comptime-known, therefore will be stored in the slice directly without having it being part of the tagname symbol. In the future this can use a jump table instead of an if-else chain, similar to the `switch` instruction.

2 files changed, 184 insertions(+), 3 deletions(-)

src/arch/wasm/CodeGen.zig+182-1
...@@ -1944,6 +1944,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1944,6 +1944,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1944 .memcpy => func.airMemcpy(inst),1944 .memcpy => func.airMemcpy(inst),
19451945
1946 .ret_addr => func.airRetAddr(inst),1946 .ret_addr => func.airRetAddr(inst),
1947 .tag_name => func.airTagName(inst),
19471948
1948 .mul_sat,1949 .mul_sat,
1949 .mod,1950 .mod,
...@@ -1962,7 +1963,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1962,7 +1963,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1962 .atomic_store_release,1963 .atomic_store_release,
1963 .atomic_store_seq_cst,1964 .atomic_store_seq_cst,
1964 .atomic_rmw,1965 .atomic_rmw,
1965 .tag_name,
1966 .err_return_trace,1966 .err_return_trace,
1967 .set_err_return_trace,1967 .set_err_return_trace,
1968 .save_err_return_trace_index,1968 .save_err_return_trace_index,
...@@ -6396,3 +6396,184 @@ fn callIntrinsic(...@@ -6396,3 +6396,184 @@ fn callIntrinsic(
6396 return WValue{ .stack = {} };6396 return WValue{ .stack = {} };
6397 }6397 }
6398}6398}
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 _ = try func.getTagNameFunction(enum_ty);
6407
6408 func.finishAir(inst, .none, &.{un_op});
6409}
6410
6411fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
6412 const enum_decl_index = enum_ty.getOwnerDecl();
6413 const module = func.bin_file.base.options.module.?;
6414
6415 // check if we already generated code for this.
6416 if (func.bin_file.decls.get(enum_decl_index)) |decl_atom_index| {
6417 std.debug.print("Found atom index for Enum decl! {d}\n", .{decl_atom_index});
6418 const atom = func.bin_file.getAtom(decl_atom_index);
6419 return atom.getSymbolIndex().?;
6420 }
6421
6422 // Create an atom in which we will store all tag names.
6423 const func_atom_index = try func.bin_file.getOrCreateAtomForDecl(enum_decl_index);
6424 const func_atom = func.bin_file.getAtomPtr(func_atom_index);
6425 std.debug.print("Generated a new atom! {d}\n", .{func_atom_index});
6426
6427 var arena_allocator = std.heap.ArenaAllocator.init(func.gpa);
6428 defer arena_allocator.deinit();
6429 const arena = arena_allocator.allocator();
6430
6431 const fqn = try module.declPtr(enum_decl_index).getFullyQualifiedName(module);
6432 defer module.gpa.free(fqn);
6433 const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn});
6434
6435 if (func.bin_file.findGlobalSymbol(func_name)) |loc| {
6436 return loc.index;
6437 }
6438
6439 const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
6440 var int_tag_type_buffer: Type.Payload.Bits = undefined;
6441 const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
6442
6443 if (int_tag_ty.bitSize(func.target) > 64) {
6444 return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{});
6445 }
6446
6447 var func_type = try genFunctype(func.gpa, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);
6448 defer func_type.deinit(func.gpa);
6449 try func.bin_file.storeDeclType(enum_decl_index, func_type);
6450
6451 var body_list = std.ArrayList(u8).init(arena);
6452 var writer = body_list.writer();
6453
6454 // The locals of the function body (always 2)
6455 try leb.writeULEB128(writer, @as(u32, 2));
6456 try leb.writeULEB128(writer, @as(u32, 1));
6457 try writer.writeByte(func.genValtype(slice_ty, func.target));
6458 try leb.writeULEB128(writer, @as(u32, 1));
6459 try writer.writeByte(func.genValtype(int_tag_ty, func.target));
6460
6461 // outer block
6462 try writer.writeByte(std.wasm.opcode(.block));
6463
6464 // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse.
6465 // generate an if-else chain for each tag value as well as constant.
6466 for (enum_ty.enumFields().keys(), 0..) |tag_name, field_index| {
6467 // for each tag name, create an atom to store its name into,
6468 // and then get a pointer to its value.
6469 const tag_atom_index = try func.bin_file.createAtom();
6470 const tag_atom = func.bin_file.getAtomPtr(tag_atom_index);
6471 tag_atom.alignment = 1;
6472 try func.bin_file.parseAtom(tag_atom_index, .read_only);
6473 try tag_atom.code.appendSlice(func.gpa, tag_name);
6474
6475 // block for this if case
6476 try writer.writeByte(std.wasm.opcode(.block));
6477
6478 // get actual tag value (stored in 2nd parameter);
6479 try writer.writeByte(std.wasm.opcode(.local_get));
6480 try leb.writeULEB128(writer, @as(u32, 1));
6481
6482 const tag_value = int: {
6483 var tag_val_payload: Value.Payload.U32 = .{
6484 .base = .{ .tag = .enum_field_index },
6485 .data = @intCast(u32, field_index),
6486 };
6487 break :int try func.lowerConstant(Value.initPayload(&tag_val_payload.base), enum_ty);
6488 };
6489
6490 switch (tag_value) {
6491 .imm32 => |value| {
6492 try writer.writeByte(std.wasm.opcode(.i32_const));
6493 try leb.writeULEB128(writer, value);
6494 try writer.writeByte(std.wasm.opcode(.i32_neq));
6495 },
6496 .imm64 => |value| {
6497 try writer.writeByte(std.wasm.opcode(.i64_const));
6498 try leb.writeULEB128(writer, value);
6499 try writer.writeByte(std.wasm.opcode(.i64_neq));
6500 },
6501 else => unreachable,
6502 }
6503 // if they're not equal, break out of current branch
6504 try writer.writeByte(std.wasm.opcode(.br_if));
6505
6506 // store the address of the tagname in the pointer field of the slice
6507 try writer.writeByte(std.wasm.opcode(.local_get));
6508 try leb.writeULEB128(writer, @as(u32, 0));
6509
6510 const is_wasm32 = func.arch() == .wasm32;
6511 // get address of tagname and emit a relocation to it
6512 {
6513 if (is_wasm32) {
6514 try writer.writeByte(std.wasm.opcode(.i32_const));
6515 var buf: [5]u8 = undefined;
6516 leb.writeUnsignedFixed(5, &buf, mem.pointer);
6517 try writer.writeAll(&buf);
6518 } else {
6519 try writer.writeByte(std.wasm.opcode(.i64_const));
6520 var buf: [10]u8 = undefined;
6521 leb.writeUnsignedFixed(10, &buf, mem.pointer);
6522 try writer.writeAll(&buf);
6523 }
6524 try func_atom.relocs.append(func.gpa, .{
6525 .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_LEB else .R_WASM_MEMORY_ADDR_LEB64,
6526 .offset = @intCast(u32, body_list.items.len),
6527 .index = tag_atom.getSymbolIndex().?,
6528 });
6529 }
6530
6531 // call the actual store instructions
6532 if (is_wasm32) {
6533 // store pointer
6534 try writer.writeByte(std.wasm.opcode(.i32_store));
6535 try leb.writeULEB128(writer, @as(u32, 4));
6536 try leb.writeULEB128(writer, @as(u32, 0));
6537
6538 // store length
6539 try writer.writeByte(std.wasm.opcode(.local_get));
6540 try leb.writeULEB128(writer, @as(u32, 0));
6541 try writer.writeByte(std.wasm.opcode(.i32_const));
6542 try leb.writeULEB128(writer, @intCast(u32, tag_name.len));
6543 try writer.writeByte(std.wasm.opcode(.i32_store));
6544 try leb.writeULEB128(writer, @as(u32, 4));
6545 try leb.writeULEB128(writer, @as(u32, 4));
6546 } else {
6547 // store pointer
6548 try writer.writeByte(std.wasm.opcode(.i64_store));
6549 try leb.writeULEB128(writer, @as(u32, 8));
6550 try leb.writeULEB128(writer, @as(u32, 0));
6551
6552 // store length
6553 try writer.writeByte(std.wasm.opcode(.local_get));
6554 try leb.writeULEB128(writer, @as(u32, 0));
6555 try writer.writeByte(std.wasm.opcode(.i64_const));
6556 try leb.writeULEB128(writer, @intCast(u64, tag_name.len));
6557 try writer.writeByte(std.wasm.opcode(.i64_store));
6558 try leb.writeULEB128(writer, @as(u32, 8));
6559 try leb.writeULEB128(writer, @as(u32, 8));
6560 }
6561
6562 // break outside blocks
6563 try writer.writeByte(std.wasm.opcode(.br));
6564 try leb.writeULEB128(writer, @as(u32, 1));
6565
6566 // end the block for this case
6567 try writer.writeByte(std.wasm.opcode(.end));
6568 }
6569
6570 try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name
6571 // finish outer block
6572 try writer.writeByte(std.wasm.opcode(.end));
6573 // finish function body
6574 try writer.writeByte(std.wasm.opcode(.end));
6575
6576 try func_atom.code.appendSlice(func.gpa, body_list.items);
6577
6578 return func_atom.sym_index;
6579}
src/link/Wasm.zig+2-2
...@@ -577,7 +577,7 @@ pub fn getOrCreateAtomForDecl(wasm: *Wasm, decl_index: Module.Decl.Index) !Atom....@@ -577,7 +577,7 @@ pub fn getOrCreateAtomForDecl(wasm: *Wasm, decl_index: Module.Decl.Index) !Atom.
577}577}
578578
579/// Creates a new empty `Atom` and returns its `Atom.Index`579/// Creates a new empty `Atom` and returns its `Atom.Index`
580fn createAtom(wasm: *Wasm) !Atom.Index {580pub fn createAtom(wasm: *Wasm) !Atom.Index {
581 const index = @intCast(Atom.Index, wasm.managed_atoms.items.len);581 const index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
582 const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);582 const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
583 atom.* = Atom.empty;583 atom.* = Atom.empty;
...@@ -1208,7 +1208,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void {...@@ -1208,7 +1208,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
12081208
1209// Tries to find a global symbol by its name. Returns null when not found,1209// Tries to find a global symbol by its name. Returns null when not found,
1210/// and its location when it is found.1210/// and its location when it is found.
1211fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {1211pub fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
1212 const offset = wasm.string_table.getOffset(name) orelse return null;1212 const offset = wasm.string_table.getOffset(name) orelse return null;
1213 return wasm.globals.get(offset);1213 return wasm.globals.get(offset);
1214}1214}