From 25fcf03a678c4fc357d1342322cfe7ca058d9c0f Mon Sep 17 00:00:00 2001 From: Pavel Verigo Date: Thu, 2 Jul 2026 23:54:41 +0200 Subject: [PATCH] stage2-wasm: support .is_named_enum_value --- src/codegen/wasm/CodeGen.zig | 43 +++++++++++++--- src/codegen/wasm/Emit.zig | 21 ++++++-- src/codegen/wasm/Mir.zig | 9 +++- src/link/Wasm.zig | 20 +++++--- src/link/Wasm/Flush.zig | 96 ++++++++++++++++-------------------- src/target.zig | 2 +- 6 files changed, 118 insertions(+), 73 deletions(-) diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index f011b1cf5e8ed8983c56a92ffe1589bd0bb35a43..4f7d1c6e9b572fbdef38e221be3dfe6434f9c4a4 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -1856,10 +1856,11 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .c_va_end => try cg.airVaEnd(inst), .c_va_start => try cg.airVaStart(inst), + .is_named_enum_value => try cg.airIsNamedEnumValue(inst), + .err_return_trace, .set_err_return_trace, .save_err_return_trace_index, - .is_named_enum_value, .addrspace_cast, => |tag| return cg.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), @@ -7279,12 +7280,37 @@ fn airTagName(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const operand = try cg.resolveInst(un_op); const enum_ty = cg.typeOf(un_op); - const result_ptr = try cg.allocStack(cg.typeOfIndex(inst)); - try cg.lowerToStack(result_ptr); + try cg.addInst(.{ .tag = .enum_tag_name_table_ref, .data = .{ .ip_index = enum_ty.toIntern() } }); try cg.lowerToStack(operand); - try cg.addInst(.{ .tag = .call_tag_name, .data = .{ .ip_index = enum_ty.toIntern() } }); + try cg.addInst(.{ .tag = .call_tag_index, .data = .{ .ip_index = enum_ty.toIntern() } }); - return cg.finishAir(inst, result_ptr, &.{un_op}); + switch (cg.ptr_size) { + .wasm32 => { + try cg.addImm32(@intCast(8)); + try cg.addTag(.i32_mul); + try cg.addTag(.i32_add); + }, + .wasm64 => { + try cg.addImm64(8); + try cg.addTag(.i64_mul); + try cg.addTag(.i64_add); + }, + } + + return cg.finishAir(inst, .stack, &.{un_op}); +} + +fn airIsNamedEnumValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op; + const operand = try cg.resolveInst(un_op); + const enum_ty = cg.typeOf(un_op); + + try cg.lowerToStack(operand); + try cg.addInst(.{ .tag = .call_tag_index, .data = .{ .ip_index = enum_ty.toIntern() } }); + try cg.addImm32(~@as(u32, 0)); + try cg.addTag(.i32_ne); + + return cg.finishAir(inst, .stack, &.{un_op}); } fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { @@ -7313,7 +7339,7 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { } if (highest) |*h| { if (err_int > h.*) { - highest = err_int; + h.* = err_int; } } else { highest = err_int; @@ -7336,10 +7362,10 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { // Account for default branch so always add '1' const depth = @as(u32, @intCast(highest.? - lowest.? + 1)); - const jump_table: Mir.JumpTable = .{ .length = depth }; + const jump_table: Mir.JumpTable = .{ .length = depth + 1 }; const table_extra_index = try cg.addExtra(jump_table); try cg.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } }); - try cg.mir_extra.ensureUnusedCapacity(cg.gpa, depth); + try cg.mir_extra.ensureUnusedCapacity(cg.gpa, depth + 1); var value: u32 = lowest.?; while (value <= highest.?) : (value += 1) { @@ -7351,6 +7377,7 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { }; cg.mir_extra.appendAssumeCapacity(idx); } + cg.mir_extra.appendAssumeCapacity(0); // outside lowest...highest try cg.endBlock(); // 'false' branch (i.e. error set does not have value diff --git a/src/codegen/wasm/Emit.zig b/src/codegen/wasm/Emit.zig index be9f681e8398c77a69227a38d5fb57d35740fe54..e0a7d449d661c4a782e3237e69a4876b2f03c41a 100644 --- a/src/codegen/wasm/Emit.zig +++ b/src/codegen/wasm/Emit.zig @@ -220,25 +220,40 @@ pub fn lowerToCode(emit: *Emit) Error!void { continue :loop tags[inst]; }, - .call_tag_name => { + .call_tag_index => { try code.ensureUnusedCapacity(gpa, 6); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call)); if (is_obj) { try wasm.out_relocs.append(gpa, .{ .offset = @intCast(code.items.len), - .pointee = .{ .symbol_index = try wasm.tagNameSymbolIndex(datas[inst].ip_index) }, + .pointee = .{ .symbol_index = try wasm.tagTableIndexSymbolIndex(datas[inst].ip_index) }, .tag = .function_index_leb, .addend = 0, }); code.appendNTimesAssumeCapacity(0, 5); } else { - appendOutputFunctionIndex(code, .fromTagNameType(wasm, datas[inst].ip_index)); + appendOutputFunctionIndex(code, .fromTagIndexType(wasm, datas[inst].ip_index)); } inst += 1; continue :loop tags[inst]; }, + .enum_tag_name_table_ref => { + try code.ensureUnusedCapacity(gpa, 11); + const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const; + code.appendAssumeCapacity(@intFromEnum(opcode)); + if (is_obj) { + @panic("TODO"); + } else { + const addr: u32 = wasm.tagIndexTableAddr(datas[inst].ip_index); + writeSleb128(code, addr); + + inst += 1; + continue :loop tags[inst]; + } + }, + .call_intrinsic => { // Although this currently uses `wasm.internString`, note that it // *could* be changed to directly index into a preloaded strings diff --git a/src/codegen/wasm/Mir.zig b/src/codegen/wasm/Mir.zig index f3b96f1c7939b4566630b8c3261cb432206d49ac..d9ff5117b454391a22d799225f0f29f097a1bca7 100644 --- a/src/codegen/wasm/Mir.zig +++ b/src/codegen/wasm/Mir.zig @@ -164,9 +164,14 @@ pub const Inst = struct { call_indirect, /// Calls a function by its index. /// - /// The function is the auto-generated tag name function for the type + /// The function is the auto-generated tag index function for the type /// provided in `ip_index`. - call_tag_name, + call_tag_index, + /// Lowers to an i32_const (wasm32) or i64_const (wasm64) containing + /// the base address of the table of enum tag names slices. + /// + /// Uses `ip_index`. + enum_tag_name_table_ref, /// Lowers to a `call` instruction, using `intrinsic`. call_intrinsic, /// Pops a value from the stack, and discards it. diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index a4d20fdb07b8166c1ee38537eda0274d67208311..40c3e412b4ac88c019cf6885e2d9e4bb1e2b329e 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -348,7 +348,7 @@ pub const FunctionIndex = enum(u32) { return fromResolution(wasm, .fromIpNav(wasm, nav_index)); } - pub fn fromTagNameType(wasm: *const Wasm, tag_type: InternPool.Index) ?FunctionIndex { + pub fn fromTagIndexType(wasm: *const Wasm, tag_type: InternPool.Index) ?FunctionIndex { const zcu_func: ZcuFunc.Index = @enumFromInt(wasm.zcu_funcs.getIndex(tag_type) orelse return null); return fromResolution(wasm, .pack(wasm, .{ .zcu_func = zcu_func })); } @@ -423,8 +423,8 @@ pub const OutputFunctionIndex = enum(u32) { return fromIpIndex(wasm, nav.resolved.?.value); } - pub fn fromTagNameType(wasm: *const Wasm, tag_type: InternPool.Index) OutputFunctionIndex { - return fromFunctionIndex(wasm, FunctionIndex.fromTagNameType(wasm, tag_type).?); + pub fn fromTagIndexType(wasm: *const Wasm, tag_type: InternPool.Index) OutputFunctionIndex { + return fromFunctionIndex(wasm, FunctionIndex.fromTagIndexType(wasm, tag_type).?); } pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex { @@ -891,8 +891,6 @@ pub const ZcuFunc = union { pub const TagName = extern struct { symbol_name: String, type_index: FunctionType.Index, - /// Index into `Wasm.tag_name_offs`. - table_index: u32, }; /// Index into `Wasm.zcu_funcs`. @@ -4015,7 +4013,7 @@ pub fn stackPointerSymbolIndex(wasm: *Wasm) Allocator.Error!SymbolTableIndex { return @enumFromInt(gop.index); } -pub fn tagNameSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Error!SymbolTableIndex { +pub fn tagTableIndexSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Error!SymbolTableIndex { const comp = wasm.base.comp; assert(comp.config.output_mode == .Obj); const gpa = comp.gpa; @@ -4173,6 +4171,16 @@ pub fn errorNameTableAddr(wasm: *Wasm) u32 { return wasm.flush_buffer.data_segments.get(.__zig_error_name_table).?; } +pub fn tagIndexTableAddr(wasm: *Wasm, ip_index: InternPool.Index) u32 { + assert(wasm.flush_buffer.memory_layout_finished); + const comp = wasm.base.comp; + assert(comp.config.output_mode != .Obj); + const f = &wasm.flush_buffer; + const table_base_addr = f.data_segments.get(.__zig_tag_name_table).?; + const table_index = f.enum_tag_name_table.get(ip_index).?; + return table_base_addr + table_index * 8; +} + fn convertZcuFnType( comp: *Compilation, cc: std.lang.CallingConvention, diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig index 165664aeba5ab76a8d117c1f4981e84151c71ada..959814d1bcb5e1248fc6b7c840277e195be41081 100644 --- a/src/link/Wasm/Flush.zig +++ b/src/link/Wasm/Flush.zig @@ -41,6 +41,8 @@ indirect_function_table: std.array_hash_map.Auto(Wasm.OutputFunctionIndex, void) /// A subset of the full interned function type list created only during flush. func_types: std.array_hash_map.Auto(Wasm.FunctionType.Index, void) = .empty, +enum_tag_name_table: std.array_hash_map.Auto(InternPool.Index, u32) = .empty, + /// For debug purposes only. memory_layout_finished: bool = false, @@ -90,6 +92,7 @@ pub fn clear(f: *Flush) void { f.binary_bytes.clearRetainingCapacity(); f.indirect_function_table.clearRetainingCapacity(); f.func_types.clearRetainingCapacity(); + f.enum_tag_name_table.clearRetainingCapacity(); f.memory_layout_finished = false; } @@ -103,6 +106,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void { f.data_imports.deinit(gpa); f.indirect_function_table.deinit(gpa); f.func_types.deinit(gpa); + f.enum_tag_name_table.deinit(gpa); f.* = undefined; } @@ -143,17 +147,24 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { try wasm.markFunctionImport(symbol_name, i.value(wasm), i); log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic }); }, - .call_tag_name => { + .call_tag_index => { assert(ip.indexToKey(data.ip_index) == .enum_type); const gop = try wasm.zcu_funcs.getOrPut(gpa, data.ip_index); if (!gop.found_existing) { - wasm.tag_name_table_ref_count += 1; const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu); gop.value_ptr.* = .{ .tag_name = .{ - .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{data.ip_index}), - .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, false, target), - .table_index = @intCast(wasm.tag_name_offs.items.len), + .symbol_name = try wasm.internStringFmt("__zig_tag_index_{d}", .{data.ip_index}), + .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .u32, false, target), } }; + } + try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {}); + }, + .enum_tag_name_table_ref => { + assert(ip.indexToKey(data.ip_index) == .enum_type); + const gop = try f.enum_tag_name_table.getOrPut(gpa, data.ip_index); + if (!gop.found_existing) { + wasm.tag_name_table_ref_count += 1; + gop.value_ptr.* = @intCast(wasm.tag_name_offs.items.len); const tag_names = ip.loadEnumType(data.ip_index).field_names; for (tag_names.get(ip)) |tag_name| { const slice = tag_name.toSlice(ip); @@ -161,7 +172,6 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]); } } - try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {}); }, else => continue, }; @@ -874,7 +884,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { const ip_index = i.key(wasm).*; switch (ip.indexToKey(ip_index)) { .enum_type => { - try emitTagNameFunction(wasm, binary_bytes, f.data_segments.get(.__zig_tag_name_table).?, i.value(wasm).tag_name.table_index, ip_index); + try emitTagIndexFunction(wasm, binary_bytes, ip_index); }, else => { const func = i.value(wasm).function; @@ -1856,11 +1866,9 @@ fn emitStartSection(gpa: Allocator, bytes: *ArrayList(u8), i: Wasm.OutputFunctio replaceVecSectionHeader(bytes, header_offset, .start, @intFromEnum(i)); } -fn emitTagNameFunction( +fn emitTagIndexFunction( wasm: *Wasm, code: *ArrayList(u8), - table_base_addr: u32, - table_index: u32, enum_type_ip: InternPool.Index, ) !void { const comp = wasm.base.comp; @@ -1870,36 +1878,37 @@ fn emitTagNameFunction( const enum_type = ip.loadEnumType(enum_type_ip); const tag_values = enum_type.field_values.get(ip); - const slice_abi_size = 8; - const encoded_alignment = @ctz(@as(u32, 4)); - if (tag_values.len == 0) { - // Auto-numbered, therefore a direct table lookup. + // Auto-numbered - try code.ensureUnusedCapacity( - gpa, - 6 * @sizeOf(std.wasm.Opcode) + - 7 * 5 + // appendReservedUleb32 - 1 * 6, // appendReservedI32Const - ); + const len = enum_type.field_names.len; + + try code.ensureUnusedCapacity(gpa, 13 + 5 * 2); appendReservedUleb32(code, 0); // no locals + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); appendReservedUleb32(code, 0); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); - appendReservedUleb32(code, 1); + appendReservedI32Const(code, len); + + // if < len -> break out of block + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_lt_u)); - appendReservedI32Const(code, slice_abi_size); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_mul)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_if)); + appendReservedUleb32(code, 0); + + // invalid -> return -1 + appendReservedI32Const(code, ~@as(u32, 0)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"return")); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load)); - appendReservedUleb32(code, encoded_alignment); - appendReservedUleb32(code, table_base_addr + table_index * 8); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store)); - appendReservedUleb32(code, encoded_alignment); + // valid -> return input + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); appendReservedUleb32(code, 0); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); @@ -1920,13 +1929,6 @@ fn emitTagNameFunction( appendReservedUleb32(code, 0); // no locals - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); - appendReservedUleb32(code, 0); - - // Outer block that computes table offset. - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); - code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.i32)); - for (tag_values, 0..) |tag_value, tag_index| { // block for this if case code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block)); @@ -1946,7 +1948,7 @@ fn emitTagNameFunction( for (0..num_limbs) |limb_index| { code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); - appendReservedUleb32(code, 1); + appendReservedUleb32(code, 0); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load)); appendReservedUleb32(code, @ctz(@as(u32, 8))); @@ -1960,7 +1962,7 @@ fn emitTagNameFunction( } } else { code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get)); - appendReservedUleb32(code, 1); + appendReservedUleb32(code, 0); switch (int_info.bits) { 0...32 => { @@ -1986,26 +1988,14 @@ fn emitTagNameFunction( appendReservedUleb32(code, 0); } - // Put the table offset of the result on the stack. - appendReservedI32Const(code, @intCast(tag_index * slice_abi_size)); - - // break outside blocks - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br)); - appendReservedUleb32(code, 1); + appendReservedI32Const(code, @intCast(tag_index)); + code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"return")); // end the block for this case code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); } - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"unreachable")); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load)); - appendReservedUleb32(code, encoded_alignment); - appendReservedUleb32(code, table_base_addr + table_index * 8); - - code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store)); - appendReservedUleb32(code, encoded_alignment); - appendReservedUleb32(code, 0); + appendReservedI32Const(code, ~@as(u32, 0)); code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end)); } diff --git a/src/target.zig b/src/target.zig index acf21d3204f54902176f1069060c346146b0430b..f5589df23b2116959cad4d422fcab4ec44919287 100644 --- a/src/target.zig +++ b/src/target.zig @@ -937,7 +937,7 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime else => false, }, .is_named_enum_value => switch (backend) { - .stage2_llvm, .stage2_x86_64 => true, + .stage2_llvm, .stage2_x86_64, .stage2_wasm => true, else => false, }, .error_set_has_value => switch (backend) { -- 2.54.0