Commit 3c27df6b13

Luuk de Gram <luuk@degram.dev>
2023-04-09 19:24:24
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.
1 parent 490a411
Changed files (2)
src
arch
link
src/arch/wasm/CodeGen.zig
@@ -1944,6 +1944,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
         .memcpy => func.airMemcpy(inst),
 
         .ret_addr => func.airRetAddr(inst),
+        .tag_name => func.airTagName(inst),
 
         .mul_sat,
         .mod,
@@ -1962,7 +1963,6 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
         .atomic_store_release,
         .atomic_store_seq_cst,
         .atomic_rmw,
-        .tag_name,
         .err_return_trace,
         .set_err_return_trace,
         .save_err_return_trace_index,
@@ -6396,3 +6396,184 @@ fn callIntrinsic(
         return WValue{ .stack = {} };
     }
 }
+
+fn airTagName(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
+    const un_op = func.air.instructions.items(.data)[inst].un_op;
+    if (func.liveness.isUnused(inst)) return func.finishAir(inst, .none, &.{un_op});
+    // const operand = try func.resolveInst(un_op);
+    const enum_ty = func.air.typeOf(un_op);
+
+    _ = try func.getTagNameFunction(enum_ty);
+
+    func.finishAir(inst, .none, &.{un_op});
+}
+
+fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
+    const enum_decl_index = enum_ty.getOwnerDecl();
+    const module = func.bin_file.base.options.module.?;
+
+    // check if we already generated code for this.
+    if (func.bin_file.decls.get(enum_decl_index)) |decl_atom_index| {
+        std.debug.print("Found atom index for Enum decl! {d}\n", .{decl_atom_index});
+        const atom = func.bin_file.getAtom(decl_atom_index);
+        return atom.getSymbolIndex().?;
+    }
+
+    // Create an atom in which we will store all tag names.
+    const func_atom_index = try func.bin_file.getOrCreateAtomForDecl(enum_decl_index);
+    const func_atom = func.bin_file.getAtomPtr(func_atom_index);
+    std.debug.print("Generated a new atom! {d}\n", .{func_atom_index});
+
+    var arena_allocator = std.heap.ArenaAllocator.init(func.gpa);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+
+    const fqn = try module.declPtr(enum_decl_index).getFullyQualifiedName(module);
+    defer module.gpa.free(fqn);
+    const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn});
+
+    if (func.bin_file.findGlobalSymbol(func_name)) |loc| {
+        return loc.index;
+    }
+
+    const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
+    var int_tag_type_buffer: Type.Payload.Bits = undefined;
+    const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
+
+    if (int_tag_ty.bitSize(func.target) > 64) {
+        return func.fail("TODO: Implement @tagName for enums with tag size larger than 64 bits", .{});
+    }
+
+    var func_type = try genFunctype(func.gpa, .Unspecified, &.{int_tag_ty}, slice_ty, func.target);
+    defer func_type.deinit(func.gpa);
+    try func.bin_file.storeDeclType(enum_decl_index, func_type);
+
+    var body_list = std.ArrayList(u8).init(arena);
+    var writer = body_list.writer();
+
+    // The locals of the function body (always 2)
+    try leb.writeULEB128(writer, @as(u32, 2));
+    try leb.writeULEB128(writer, @as(u32, 1));
+    try writer.writeByte(func.genValtype(slice_ty, func.target));
+    try leb.writeULEB128(writer, @as(u32, 1));
+    try writer.writeByte(func.genValtype(int_tag_ty, func.target));
+
+    // outer block
+    try writer.writeByte(std.wasm.opcode(.block));
+
+    // TODO: Make switch implementation generic so we can use a jump table for this when the tags are not sparse.
+    // generate an if-else chain for each tag value as well as constant.
+    for (enum_ty.enumFields().keys(), 0..) |tag_name, field_index| {
+        // for each tag name, create an atom to store its name into,
+        // and then get a pointer to its value.
+        const tag_atom_index = try func.bin_file.createAtom();
+        const tag_atom = func.bin_file.getAtomPtr(tag_atom_index);
+        tag_atom.alignment = 1;
+        try func.bin_file.parseAtom(tag_atom_index, .read_only);
+        try tag_atom.code.appendSlice(func.gpa, tag_name);
+
+        // block for this if case
+        try writer.writeByte(std.wasm.opcode(.block));
+
+        // get actual tag value (stored in 2nd parameter);
+        try writer.writeByte(std.wasm.opcode(.local_get));
+        try leb.writeULEB128(writer, @as(u32, 1));
+
+        const tag_value = int: {
+            var tag_val_payload: Value.Payload.U32 = .{
+                .base = .{ .tag = .enum_field_index },
+                .data = @intCast(u32, field_index),
+            };
+            break :int try func.lowerConstant(Value.initPayload(&tag_val_payload.base), enum_ty);
+        };
+
+        switch (tag_value) {
+            .imm32 => |value| {
+                try writer.writeByte(std.wasm.opcode(.i32_const));
+                try leb.writeULEB128(writer, value);
+                try writer.writeByte(std.wasm.opcode(.i32_neq));
+            },
+            .imm64 => |value| {
+                try writer.writeByte(std.wasm.opcode(.i64_const));
+                try leb.writeULEB128(writer, value);
+                try writer.writeByte(std.wasm.opcode(.i64_neq));
+            },
+            else => unreachable,
+        }
+        // if they're not equal, break out of current branch
+        try writer.writeByte(std.wasm.opcode(.br_if));
+
+        // store the address of the tagname in the pointer field of the slice
+        try writer.writeByte(std.wasm.opcode(.local_get));
+        try leb.writeULEB128(writer, @as(u32, 0));
+
+        const is_wasm32 = func.arch() == .wasm32;
+        // get address of tagname and emit a relocation to it
+        {
+            if (is_wasm32) {
+                try writer.writeByte(std.wasm.opcode(.i32_const));
+                var buf: [5]u8 = undefined;
+                leb.writeUnsignedFixed(5, &buf, mem.pointer);
+                try writer.writeAll(&buf);
+            } else {
+                try writer.writeByte(std.wasm.opcode(.i64_const));
+                var buf: [10]u8 = undefined;
+                leb.writeUnsignedFixed(10, &buf, mem.pointer);
+                try writer.writeAll(&buf);
+            }
+            try func_atom.relocs.append(func.gpa, .{
+                .relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_LEB else .R_WASM_MEMORY_ADDR_LEB64,
+                .offset = @intCast(u32, body_list.items.len),
+                .index = tag_atom.getSymbolIndex().?,
+            });
+        }
+
+        // call the actual store instructions
+        if (is_wasm32) {
+            // store pointer
+            try writer.writeByte(std.wasm.opcode(.i32_store));
+            try leb.writeULEB128(writer, @as(u32, 4));
+            try leb.writeULEB128(writer, @as(u32, 0));
+
+            // store length
+            try writer.writeByte(std.wasm.opcode(.local_get));
+            try leb.writeULEB128(writer, @as(u32, 0));
+            try writer.writeByte(std.wasm.opcode(.i32_const));
+            try leb.writeULEB128(writer, @intCast(u32, tag_name.len));
+            try writer.writeByte(std.wasm.opcode(.i32_store));
+            try leb.writeULEB128(writer, @as(u32, 4));
+            try leb.writeULEB128(writer, @as(u32, 4));
+        } else {
+            // store pointer
+            try writer.writeByte(std.wasm.opcode(.i64_store));
+            try leb.writeULEB128(writer, @as(u32, 8));
+            try leb.writeULEB128(writer, @as(u32, 0));
+
+            // store length
+            try writer.writeByte(std.wasm.opcode(.local_get));
+            try leb.writeULEB128(writer, @as(u32, 0));
+            try writer.writeByte(std.wasm.opcode(.i64_const));
+            try leb.writeULEB128(writer, @intCast(u64, tag_name.len));
+            try writer.writeByte(std.wasm.opcode(.i64_store));
+            try leb.writeULEB128(writer, @as(u32, 8));
+            try leb.writeULEB128(writer, @as(u32, 8));
+        }
+
+        // break outside blocks
+        try writer.writeByte(std.wasm.opcode(.br));
+        try leb.writeULEB128(writer, @as(u32, 1));
+
+        // end the block for this case
+        try writer.writeByte(std.wasm.opcode(.end));
+    }
+
+    try writer.writeByte(std.wasm.opcode(.@"unreachable")); // tag value does not have a name
+    // finish outer block
+    try writer.writeByte(std.wasm.opcode(.end));
+    // finish function body
+    try writer.writeByte(std.wasm.opcode(.end));
+
+    try func_atom.code.appendSlice(func.gpa, body_list.items);
+
+    return func_atom.sym_index;
+}
src/link/Wasm.zig
@@ -577,7 +577,7 @@ pub fn getOrCreateAtomForDecl(wasm: *Wasm, decl_index: Module.Decl.Index) !Atom.
 }
 
 /// Creates a new empty `Atom` and returns its `Atom.Index`
-fn createAtom(wasm: *Wasm) !Atom.Index {
+pub fn createAtom(wasm: *Wasm) !Atom.Index {
     const index = @intCast(Atom.Index, wasm.managed_atoms.items.len);
     const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
     atom.* = Atom.empty;
@@ -1208,7 +1208,7 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
 
 // Tries to find a global symbol by its name. Returns null when not found,
 /// and its location when it is found.
-fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
+pub fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
     const offset = wasm.string_table.getOffset(name) orelse return null;
     return wasm.globals.get(offset);
 }