authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-07-02 23:54:41+02:00
committergravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-07-03 22:14:13+02:00
log25fcf03a678c4fc357d1342322cfe7ca058d9c0f
treed5fde2e5e7720704c4d62a1d29cb784ae6ae01ed
parent2f7e082381e64bbcdd06cf8f06eb84a9c8ad67de

stage2-wasm: support .is_named_enum_value


6 files changed, 118 insertions(+), 73 deletions(-)

src/codegen/wasm/CodeGen.zig+35-8
......@@ -1856,10 +1856,11 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
18561856 .c_va_end => try cg.airVaEnd(inst),
18571857 .c_va_start => try cg.airVaStart(inst),
18581858
1859 .is_named_enum_value => try cg.airIsNamedEnumValue(inst),
1860
18591861 .err_return_trace,
18601862 .set_err_return_trace,
18611863 .save_err_return_trace_index,
1862 .is_named_enum_value,
18631864 .addrspace_cast,
18641865 => |tag| return cg.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
18651866
......@@ -7279,12 +7280,37 @@ fn airTagName(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
72797280 const operand = try cg.resolveInst(un_op);
72807281 const enum_ty = cg.typeOf(un_op);
72817282
7282 const result_ptr = try cg.allocStack(cg.typeOfIndex(inst));
7283 try cg.lowerToStack(result_ptr);
7283 try cg.addInst(.{ .tag = .enum_tag_name_table_ref, .data = .{ .ip_index = enum_ty.toIntern() } });
7284 try cg.lowerToStack(operand);
7285 try cg.addInst(.{ .tag = .call_tag_index, .data = .{ .ip_index = enum_ty.toIntern() } });
7286
7287 switch (cg.ptr_size) {
7288 .wasm32 => {
7289 try cg.addImm32(@intCast(8));
7290 try cg.addTag(.i32_mul);
7291 try cg.addTag(.i32_add);
7292 },
7293 .wasm64 => {
7294 try cg.addImm64(8);
7295 try cg.addTag(.i64_mul);
7296 try cg.addTag(.i64_add);
7297 },
7298 }
7299
7300 return cg.finishAir(inst, .stack, &.{un_op});
7301}
7302
7303fn airIsNamedEnumValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
7304 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
7305 const operand = try cg.resolveInst(un_op);
7306 const enum_ty = cg.typeOf(un_op);
7307
72847308 try cg.lowerToStack(operand);
7285 try cg.addInst(.{ .tag = .call_tag_name, .data = .{ .ip_index = enum_ty.toIntern() } });
7309 try cg.addInst(.{ .tag = .call_tag_index, .data = .{ .ip_index = enum_ty.toIntern() } });
7310 try cg.addImm32(~@as(u32, 0));
7311 try cg.addTag(.i32_ne);
72867312
7287 return cg.finishAir(inst, result_ptr, &.{un_op});
7313 return cg.finishAir(inst, .stack, &.{un_op});
72887314}
72897315
72907316fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
......@@ -7313,7 +7339,7 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
73137339 }
73147340 if (highest) |*h| {
73157341 if (err_int > h.*) {
7316 highest = err_int;
7342 h.* = err_int;
73177343 }
73187344 } else {
73197345 highest = err_int;
......@@ -7336,10 +7362,10 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
73367362
73377363 // Account for default branch so always add '1'
73387364 const depth = @as(u32, @intCast(highest.? - lowest.? + 1));
7339 const jump_table: Mir.JumpTable = .{ .length = depth };
7365 const jump_table: Mir.JumpTable = .{ .length = depth + 1 };
73407366 const table_extra_index = try cg.addExtra(jump_table);
73417367 try cg.addInst(.{ .tag = .br_table, .data = .{ .payload = table_extra_index } });
7342 try cg.mir_extra.ensureUnusedCapacity(cg.gpa, depth);
7368 try cg.mir_extra.ensureUnusedCapacity(cg.gpa, depth + 1);
73437369
73447370 var value: u32 = lowest.?;
73457371 while (value <= highest.?) : (value += 1) {
......@@ -7351,6 +7377,7 @@ fn airErrorSetHasValue(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
73517377 };
73527378 cg.mir_extra.appendAssumeCapacity(idx);
73537379 }
7380 cg.mir_extra.appendAssumeCapacity(0); // outside lowest...highest
73547381 try cg.endBlock();
73557382
73567383 // 'false' branch (i.e. error set does not have value
src/codegen/wasm/Emit.zig+18-3
......@@ -220,25 +220,40 @@ pub fn lowerToCode(emit: *Emit) Error!void {
220220 continue :loop tags[inst];
221221 },
222222
223 .call_tag_name => {
223 .call_tag_index => {
224224 try code.ensureUnusedCapacity(gpa, 6);
225225 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call));
226226 if (is_obj) {
227227 try wasm.out_relocs.append(gpa, .{
228228 .offset = @intCast(code.items.len),
229 .pointee = .{ .symbol_index = try wasm.tagNameSymbolIndex(datas[inst].ip_index) },
229 .pointee = .{ .symbol_index = try wasm.tagTableIndexSymbolIndex(datas[inst].ip_index) },
230230 .tag = .function_index_leb,
231231 .addend = 0,
232232 });
233233 code.appendNTimesAssumeCapacity(0, 5);
234234 } else {
235 appendOutputFunctionIndex(code, .fromTagNameType(wasm, datas[inst].ip_index));
235 appendOutputFunctionIndex(code, .fromTagIndexType(wasm, datas[inst].ip_index));
236236 }
237237
238238 inst += 1;
239239 continue :loop tags[inst];
240240 },
241241
242 .enum_tag_name_table_ref => {
243 try code.ensureUnusedCapacity(gpa, 11);
244 const opcode: std.wasm.Opcode = if (is_wasm32) .i32_const else .i64_const;
245 code.appendAssumeCapacity(@intFromEnum(opcode));
246 if (is_obj) {
247 @panic("TODO");
248 } else {
249 const addr: u32 = wasm.tagIndexTableAddr(datas[inst].ip_index);
250 writeSleb128(code, addr);
251
252 inst += 1;
253 continue :loop tags[inst];
254 }
255 },
256
242257 .call_intrinsic => {
243258 // Although this currently uses `wasm.internString`, note that it
244259 // *could* be changed to directly index into a preloaded strings
src/codegen/wasm/Mir.zig+7-2
......@@ -164,9 +164,14 @@ pub const Inst = struct {
164164 call_indirect,
165165 /// Calls a function by its index.
166166 ///
167 /// The function is the auto-generated tag name function for the type
167 /// The function is the auto-generated tag index function for the type
168168 /// provided in `ip_index`.
169 call_tag_name,
169 call_tag_index,
170 /// Lowers to an i32_const (wasm32) or i64_const (wasm64) containing
171 /// the base address of the table of enum tag names slices.
172 ///
173 /// Uses `ip_index`.
174 enum_tag_name_table_ref,
170175 /// Lowers to a `call` instruction, using `intrinsic`.
171176 call_intrinsic,
172177 /// Pops a value from the stack, and discards it.
src/link/Wasm.zig+14-6
......@@ -348,7 +348,7 @@ pub const FunctionIndex = enum(u32) {
348348 return fromResolution(wasm, .fromIpNav(wasm, nav_index));
349349 }
350350
351 pub fn fromTagNameType(wasm: *const Wasm, tag_type: InternPool.Index) ?FunctionIndex {
351 pub fn fromTagIndexType(wasm: *const Wasm, tag_type: InternPool.Index) ?FunctionIndex {
352352 const zcu_func: ZcuFunc.Index = @enumFromInt(wasm.zcu_funcs.getIndex(tag_type) orelse return null);
353353 return fromResolution(wasm, .pack(wasm, .{ .zcu_func = zcu_func }));
354354 }
......@@ -423,8 +423,8 @@ pub const OutputFunctionIndex = enum(u32) {
423423 return fromIpIndex(wasm, nav.resolved.?.value);
424424 }
425425
426 pub fn fromTagNameType(wasm: *const Wasm, tag_type: InternPool.Index) OutputFunctionIndex {
427 return fromFunctionIndex(wasm, FunctionIndex.fromTagNameType(wasm, tag_type).?);
426 pub fn fromTagIndexType(wasm: *const Wasm, tag_type: InternPool.Index) OutputFunctionIndex {
427 return fromFunctionIndex(wasm, FunctionIndex.fromTagIndexType(wasm, tag_type).?);
428428 }
429429
430430 pub fn fromSymbolName(wasm: *const Wasm, name: String) OutputFunctionIndex {
......@@ -891,8 +891,6 @@ pub const ZcuFunc = union {
891891 pub const TagName = extern struct {
892892 symbol_name: String,
893893 type_index: FunctionType.Index,
894 /// Index into `Wasm.tag_name_offs`.
895 table_index: u32,
896894 };
897895
898896 /// Index into `Wasm.zcu_funcs`.
......@@ -4015,7 +4013,7 @@ pub fn stackPointerSymbolIndex(wasm: *Wasm) Allocator.Error!SymbolTableIndex {
40154013 return @enumFromInt(gop.index);
40164014}
40174015
4018pub fn tagNameSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Error!SymbolTableIndex {
4016pub fn tagTableIndexSymbolIndex(wasm: *Wasm, ip_index: InternPool.Index) Allocator.Error!SymbolTableIndex {
40194017 const comp = wasm.base.comp;
40204018 assert(comp.config.output_mode == .Obj);
40214019 const gpa = comp.gpa;
......@@ -4173,6 +4171,16 @@ pub fn errorNameTableAddr(wasm: *Wasm) u32 {
41734171 return wasm.flush_buffer.data_segments.get(.__zig_error_name_table).?;
41744172}
41754173
4174pub fn tagIndexTableAddr(wasm: *Wasm, ip_index: InternPool.Index) u32 {
4175 assert(wasm.flush_buffer.memory_layout_finished);
4176 const comp = wasm.base.comp;
4177 assert(comp.config.output_mode != .Obj);
4178 const f = &wasm.flush_buffer;
4179 const table_base_addr = f.data_segments.get(.__zig_tag_name_table).?;
4180 const table_index = f.enum_tag_name_table.get(ip_index).?;
4181 return table_base_addr + table_index * 8;
4182}
4183
41764184fn convertZcuFnType(
41774185 comp: *Compilation,
41784186 cc: std.lang.CallingConvention,
src/link/Wasm/Flush.zig+43-53
......@@ -41,6 +41,8 @@ indirect_function_table: std.array_hash_map.Auto(Wasm.OutputFunctionIndex, void)
4141/// A subset of the full interned function type list created only during flush.
4242func_types: std.array_hash_map.Auto(Wasm.FunctionType.Index, void) = .empty,
4343
44enum_tag_name_table: std.array_hash_map.Auto(InternPool.Index, u32) = .empty,
45
4446/// For debug purposes only.
4547memory_layout_finished: bool = false,
4648
......@@ -90,6 +92,7 @@ pub fn clear(f: *Flush) void {
9092 f.binary_bytes.clearRetainingCapacity();
9193 f.indirect_function_table.clearRetainingCapacity();
9294 f.func_types.clearRetainingCapacity();
95 f.enum_tag_name_table.clearRetainingCapacity();
9396 f.memory_layout_finished = false;
9497}
9598
......@@ -103,6 +106,7 @@ pub fn deinit(f: *Flush, gpa: Allocator) void {
103106 f.data_imports.deinit(gpa);
104107 f.indirect_function_table.deinit(gpa);
105108 f.func_types.deinit(gpa);
109 f.enum_tag_name_table.deinit(gpa);
106110 f.* = undefined;
107111}
108112
......@@ -143,17 +147,24 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
143147 try wasm.markFunctionImport(symbol_name, i.value(wasm), i);
144148 log.debug("markFunctionImport intrinsic {d}={t}", .{ i, data.intrinsic });
145149 },
146 .call_tag_name => {
150 .call_tag_index => {
147151 assert(ip.indexToKey(data.ip_index) == .enum_type);
148152 const gop = try wasm.zcu_funcs.getOrPut(gpa, data.ip_index);
149153 if (!gop.found_existing) {
150 wasm.tag_name_table_ref_count += 1;
151154 const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
152155 gop.value_ptr.* = .{ .tag_name = .{
153 .symbol_name = try wasm.internStringFmt("__zig_tag_name_{d}", .{data.ip_index}),
154 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, false, target),
155 .table_index = @intCast(wasm.tag_name_offs.items.len),
156 .symbol_name = try wasm.internStringFmt("__zig_tag_index_{d}", .{data.ip_index}),
157 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .u32, false, target),
156158 } };
159 }
160 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
161 },
162 .enum_tag_name_table_ref => {
163 assert(ip.indexToKey(data.ip_index) == .enum_type);
164 const gop = try f.enum_tag_name_table.getOrPut(gpa, data.ip_index);
165 if (!gop.found_existing) {
166 wasm.tag_name_table_ref_count += 1;
167 gop.value_ptr.* = @intCast(wasm.tag_name_offs.items.len);
157168 const tag_names = ip.loadEnumType(data.ip_index).field_names;
158169 for (tag_names.get(ip)) |tag_name| {
159170 const slice = tag_name.toSlice(ip);
......@@ -161,7 +172,6 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
161172 try wasm.tag_name_bytes.appendSlice(gpa, slice[0 .. slice.len + 1]);
162173 }
163174 }
164 try wasm.functions.put(gpa, .fromZcuFunc(wasm, @enumFromInt(gop.index)), {});
165175 },
166176 else => continue,
167177 };
......@@ -874,7 +884,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
874884 const ip_index = i.key(wasm).*;
875885 switch (ip.indexToKey(ip_index)) {
876886 .enum_type => {
877 try emitTagNameFunction(wasm, binary_bytes, f.data_segments.get(.__zig_tag_name_table).?, i.value(wasm).tag_name.table_index, ip_index);
887 try emitTagIndexFunction(wasm, binary_bytes, ip_index);
878888 },
879889 else => {
880890 const func = i.value(wasm).function;
......@@ -1856,11 +1866,9 @@ fn emitStartSection(gpa: Allocator, bytes: *ArrayList(u8), i: Wasm.OutputFunctio
18561866 replaceVecSectionHeader(bytes, header_offset, .start, @intFromEnum(i));
18571867}
18581868
1859fn emitTagNameFunction(
1869fn emitTagIndexFunction(
18601870 wasm: *Wasm,
18611871 code: *ArrayList(u8),
1862 table_base_addr: u32,
1863 table_index: u32,
18641872 enum_type_ip: InternPool.Index,
18651873) !void {
18661874 const comp = wasm.base.comp;
......@@ -1870,36 +1878,37 @@ fn emitTagNameFunction(
18701878 const enum_type = ip.loadEnumType(enum_type_ip);
18711879 const tag_values = enum_type.field_values.get(ip);
18721880
1873 const slice_abi_size = 8;
1874 const encoded_alignment = @ctz(@as(u32, 4));
1875
18761881 if (tag_values.len == 0) {
1877 // Auto-numbered, therefore a direct table lookup.
1882 // Auto-numbered
1883
1884 const len = enum_type.field_names.len;
18781885
1879 try code.ensureUnusedCapacity(
1880 gpa,
1881 6 * @sizeOf(std.wasm.Opcode) +
1882 7 * 5 + // appendReservedUleb32
1883 1 * 6, // appendReservedI32Const
1884 );
1886 try code.ensureUnusedCapacity(gpa, 13 + 5 * 2);
18851887
18861888 appendReservedUleb32(code, 0); // no locals
18871889
1890 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block));
1891 code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.empty));
1892
18881893 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
18891894 appendReservedUleb32(code, 0);
18901895
1891 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1892 appendReservedUleb32(code, 1);
1896 appendReservedI32Const(code, len);
1897
1898 // if < len -> break out of block
1899 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_lt_u));
18931900
1894 appendReservedI32Const(code, slice_abi_size);
1895 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i32_mul));
1901 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br_if));
1902 appendReservedUleb32(code, 0);
18961903
1897 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load));
1898 appendReservedUleb32(code, encoded_alignment);
1899 appendReservedUleb32(code, table_base_addr + table_index * 8);
1904 // invalid -> return -1
1905 appendReservedI32Const(code, ~@as(u32, 0));
1906 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"return"));
19001907
1901 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store));
1902 appendReservedUleb32(code, encoded_alignment);
1908 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
1909
1910 // valid -> return input
1911 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
19031912 appendReservedUleb32(code, 0);
19041913
19051914 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
......@@ -1920,13 +1929,6 @@ fn emitTagNameFunction(
19201929
19211930 appendReservedUleb32(code, 0); // no locals
19221931
1923 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1924 appendReservedUleb32(code, 0);
1925
1926 // Outer block that computes table offset.
1927 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block));
1928 code.appendAssumeCapacity(@intFromEnum(std.wasm.BlockType.i32));
1929
19301932 for (tag_values, 0..) |tag_value, tag_index| {
19311933 // block for this if case
19321934 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.block));
......@@ -1946,7 +1948,7 @@ fn emitTagNameFunction(
19461948
19471949 for (0..num_limbs) |limb_index| {
19481950 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1949 appendReservedUleb32(code, 1);
1951 appendReservedUleb32(code, 0);
19501952
19511953 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load));
19521954 appendReservedUleb32(code, @ctz(@as(u32, 8)));
......@@ -1960,7 +1962,7 @@ fn emitTagNameFunction(
19601962 }
19611963 } else {
19621964 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.local_get));
1963 appendReservedUleb32(code, 1);
1965 appendReservedUleb32(code, 0);
19641966
19651967 switch (int_info.bits) {
19661968 0...32 => {
......@@ -1986,26 +1988,14 @@ fn emitTagNameFunction(
19861988 appendReservedUleb32(code, 0);
19871989 }
19881990
1989 // Put the table offset of the result on the stack.
1990 appendReservedI32Const(code, @intCast(tag_index * slice_abi_size));
1991
1992 // break outside blocks
1993 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.br));
1994 appendReservedUleb32(code, 1);
1991 appendReservedI32Const(code, @intCast(tag_index));
19951992
1993 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"return"));
19961994 // end the block for this case
19971995 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
19981996 }
1999 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"unreachable"));
2000 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
2001
2002 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_load));
2003 appendReservedUleb32(code, encoded_alignment);
2004 appendReservedUleb32(code, table_base_addr + table_index * 8);
20051997
2006 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.i64_store));
2007 appendReservedUleb32(code, encoded_alignment);
2008 appendReservedUleb32(code, 0);
1998 appendReservedI32Const(code, ~@as(u32, 0));
20091999
20102000 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.end));
20112001}
src/target.zig+1-1
......@@ -937,7 +937,7 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime
937937 else => false,
938938 },
939939 .is_named_enum_value => switch (backend) {
940 .stage2_llvm, .stage2_x86_64 => true,
940 .stage2_llvm, .stage2_x86_64, .stage2_wasm => true,
941941 else => false,
942942 },
943943 .error_set_has_value => switch (backend) {