authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2026-07-01 16:59:35+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-07-01 23:58:49+02:00
log4805b6f7acc07223a41b38e51a9a6e4f020dfdfe
treefe8d8a128c71db31972d0e097402b26a70fa471a
parent69d65ae472e725726d3c33f23c486d2c871ef8c7

stage2-wasm: support varargs


5 files changed, 190 insertions(+), 41 deletions(-)

src/codegen/wasm/CodeGen.zig+175-28
......@@ -196,6 +196,9 @@ args: []WValue,
196196/// When it returns a pointer to the stack, the `.local` tag will be active and must be populated
197197/// before this function returns its execution to the caller.
198198return_value: WValue,
199/// Only populated for variadic functions.
200/// Holds the hidden final parameter pointing to the varargs buffer.
201varargs: WValue,
199202/// The size of the stack this function occupies. In the function prologue
200203/// we will move the stack pointer by this number, forward aligned with the `stack_alignment`.
201204stack_size: u32 = 0,
......@@ -308,7 +311,7 @@ const WValue = union(enum) {
308311 fn free(value: *WValue, gen: *CodeGen) void {
309312 if (value.* != .local) return;
310313 const local_value = value.local.value;
311 const reserved = gen.args.len + @intFromBool(gen.return_value != .none);
314 const reserved = gen.args.len + @intFromBool(gen.return_value != .none) + @intFromBool(gen.varargs != .none);
312315 if (local_value < reserved + 2) return; // reserved locals may never be re-used. Also accounts for 2 stack locals.
313316
314317 const index = local_value - reserved;
......@@ -800,6 +803,7 @@ pub fn generate(
800803 .func_index = func_index,
801804 .args = cc_result.args,
802805 .return_value = cc_result.return_value,
806 .varargs = cc_result.varargs,
803807 .local_index = cc_result.local_index,
804808 .mir_instructions = .empty,
805809 .mir_extra = .empty,
......@@ -874,6 +878,7 @@ fn generateInner(cg: *CodeGen, any_returns: bool) InnerError!Mir {
874878const CallWValues = struct {
875879 args: []WValue,
876880 return_value: WValue,
881 varargs: WValue,
877882 local_index: u32,
878883
879884 fn deinit(values: *CallWValues, gpa: Allocator) void {
......@@ -895,6 +900,7 @@ fn resolveCallingConventionValues(
895900 var result: CallWValues = .{
896901 .args = &.{},
897902 .return_value = .none,
903 .varargs = .none,
898904 .local_index = 0,
899905 };
900906 if (cc == .naked) return result;
......@@ -945,6 +951,12 @@ fn resolveCallingConventionValues(
945951 },
946952 else => unreachable, // Frontend is responsible for emitting an error earlier.
947953 }
954
955 if (fn_info.is_var_args) {
956 result.varargs = .{ .local = .{ .value = result.local_index, .references = 1 } };
957 result.local_index += 1;
958 }
959
948960 result.args = try args.toOwnedSlice();
949961 return result;
950962}
......@@ -1056,9 +1068,6 @@ fn allocStack(cg: *CodeGen, ty: Type) !WValue {
10561068 const pt = cg.pt;
10571069 const zcu = pt.zcu;
10581070 assert(ty.hasRuntimeBits(zcu));
1059 if (cg.initial_stack_value == .none) {
1060 try cg.initializeStack();
1061 }
10621071
10631072 const abi_size = std.math.cast(u32, ty.abiSize(zcu)) orelse {
10641073 return cg.fail("Type {f} with ABI size of {d} exceeds stack frame size", .{
......@@ -1067,28 +1076,29 @@ fn allocStack(cg: *CodeGen, ty: Type) !WValue {
10671076 };
10681077 const abi_align = ty.abiAlignment(zcu);
10691078
1070 cg.stack_alignment = cg.stack_alignment.max(abi_align);
1071
1072 const offset: u32 = @intCast(abi_align.forward(cg.stack_size));
1073 defer cg.stack_size = offset + abi_size;
1074
1075 return .{ .stack_offset = .{ .value = offset, .references = 1 } };
1079 return cg.allocStackBytes(abi_size, abi_align);
10761080}
10771081
10781082fn allocInt(cg: *CodeGen, int_ty: IntType) !WValue {
1079 if (cg.initial_stack_value == .none) {
1080 try cg.initializeStack();
1081 }
1082
10831083 const abi_size = std.math.cast(u32, std.zig.target.intByteSize(cg.target, int_ty.bits)) orelse {
10841084 return cg.fail("Integer ABI size exceeds max stack size", .{});
10851085 };
10861086 const abi_align: Alignment = .fromByteUnits(std.zig.target.intAlignment(cg.target, int_ty.bits));
10871087
1088 cg.stack_alignment = cg.stack_alignment.max(abi_align);
1088 return cg.allocStackBytes(abi_size, abi_align);
1089}
1090
1091fn allocStackBytes(cg: *CodeGen, size: u32, alignment: Alignment) !WValue {
1092 assert(size > 0);
10891093
1090 const offset: u32 = @intCast(abi_align.forward(cg.stack_size));
1091 defer cg.stack_size = offset + abi_size;
1094 if (cg.initial_stack_value == .none) {
1095 try cg.initializeStack();
1096 }
1097
1098 cg.stack_alignment = cg.stack_alignment.max(alignment);
1099
1100 const offset: u32 = @intCast(alignment.forward(cg.stack_size));
1101 defer cg.stack_size = offset + size;
10921102
10931103 return .{ .stack_offset = .{ .value = offset, .references = 1 } };
10941104}
......@@ -1841,15 +1851,16 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
18411851
18421852 .assembly => cg.airAsm(inst),
18431853
1854 .c_va_arg => try cg.airVaArg(inst),
1855 .c_va_copy => try cg.airVaCopy(inst),
1856 .c_va_end => try cg.airVaEnd(inst),
1857 .c_va_start => try cg.airVaStart(inst),
1858
18441859 .err_return_trace,
18451860 .set_err_return_trace,
18461861 .save_err_return_trace_index,
18471862 .is_named_enum_value,
18481863 .addrspace_cast,
1849 .c_va_arg,
1850 .c_va_copy,
1851 .c_va_end,
1852 .c_va_start,
18531864 => |tag| return cg.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
18541865
18551866 .atomic_load => cg.airAtomicLoad(inst),
......@@ -2030,19 +2041,70 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier)
20302041 return cg.fail("unable to lower callee to a function index", .{});
20312042 };
20322043
2033 const sret: WValue = if (first_param_sret) blk: {
2034 const sret_local = try cg.allocStack(ret_ty);
2035 try cg.lowerToStack(sret_local);
2036 break :blk sret_local;
2044 const sret: WValue = if (first_param_sret)
2045 try cg.allocStack(ret_ty)
2046 else
2047 .none;
2048
2049 const fixed_arg_count = fn_info.param_types.len;
2050
2051 const varargs_buf: WValue = if (fn_info.is_var_args) buf: {
2052 var varargs_size: u32 = 0;
2053 var varargs_align: Alignment = .fromByteUnits(1);
2054
2055 for (args[fixed_arg_count..]) |arg| {
2056 const arg_ty = cg.typeOf(arg);
2057 if (!arg_ty.hasRuntimeBits(zcu)) continue;
2058
2059 const arg_size = std.math.cast(u32, arg_ty.abiSize(zcu)) orelse {
2060 return cg.fail("argument type {f} too large for wasm varargs buffer", .{arg_ty.fmt(pt)});
2061 };
2062 const arg_align = arg_ty.abiAlignment(zcu);
2063
2064 varargs_align = varargs_align.max(arg_align);
2065 varargs_size = @intCast(arg_align.forward(varargs_size));
2066 varargs_size += arg_size;
2067 }
2068
2069 if (varargs_size == 0) varargs_size = 1;
2070
2071 const buffer = try cg.allocStackBytes(varargs_size, varargs_align);
2072
2073 var offset: u32 = 0;
2074 for (args[fixed_arg_count..]) |arg| {
2075 const arg_ty = cg.typeOf(arg);
2076 if (!arg_ty.hasRuntimeBits(zcu)) continue;
2077
2078 const arg_val = try cg.resolveInst(arg);
2079 const arg_size = std.math.cast(u32, arg_ty.abiSize(zcu)) orelse {
2080 return cg.fail("argument type {f} too large for wasm varargs buffer", .{arg_ty.fmt(pt)});
2081 };
2082 const arg_align = arg_ty.abiAlignment(zcu);
2083
2084 offset = @intCast(arg_align.forward(offset));
2085 try cg.store(buffer, arg_val, arg_ty, offset);
2086 offset += arg_size;
2087 }
2088
2089 break :buf buffer;
20372090 } else .none;
20382091
2039 for (args) |arg| {
2040 const arg_val = try cg.resolveInst(arg);
2092 if (first_param_sret) {
2093 try cg.lowerToStack(sret);
2094 }
2095
2096 for (args, 0..) |arg, arg_i| {
2097 if (fn_info.is_var_args and arg_i >= fixed_arg_count) break;
20412098
20422099 const arg_ty = cg.typeOf(arg);
20432100 if (!arg_ty.hasRuntimeBits(zcu)) continue;
20442101
2045 try cg.lowerArg(zcu.typeToFunc(fn_ty).?.cc, arg_ty, arg_val);
2102 const arg_val = try cg.resolveInst(arg);
2103 try cg.lowerArg(fn_info.cc, arg_ty, arg_val);
2104 }
2105
2106 if (fn_info.is_var_args) {
2107 try cg.lowerToStack(varargs_buf);
20462108 }
20472109
20482110 if (callee) |nav_index| {
......@@ -2097,6 +2159,91 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.lang.CallModifier)
20972159 return cg.finishAirResult(inst, result_value);
20982160}
20992161
2162fn airVaStart(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2163 try cg.emitWValue(cg.varargs);
2164 return cg.finishAir(inst, .stack, &.{});
2165}
2166
2167fn airVaEnd(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2168 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2169 return cg.finishAir(inst, .none, &.{un_op});
2170}
2171
2172fn airVaCopy(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2173 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2174 const operand = try cg.resolveInst(ty_op.operand);
2175
2176 const result = try cg.load(operand, .usize, 0);
2177
2178 return cg.finishAir(inst, result, &.{ty_op.operand});
2179}
2180
2181fn airVaArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2182 const zcu = cg.pt.zcu;
2183 const ty_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2184 const operand = try cg.resolveInst(ty_op.operand);
2185
2186 const ty = cg.typeOfIndex(inst);
2187
2188 if (!ty.hasRuntimeBits(zcu)) {
2189 return cg.finishAir(inst, .none, &.{ty_op.operand});
2190 }
2191
2192 const is_f32_va_arg = ty.toIntern() == .f32_type;
2193 const load_ty: Type = if (is_f32_va_arg) Type.f64 else ty;
2194
2195 const abi_size: u32 = @intCast(load_ty.abiSize(zcu));
2196 const abi_align: u32 = @intCast(load_ty.abiAlignment(zcu).toByteUnits().?);
2197
2198 const arg_ptr = try cg.allocLocal(.usize);
2199 _ = try cg.load(operand, .usize, 0);
2200
2201 if (abi_align > 1) {
2202 switch (cg.ptr_size) {
2203 .wasm32 => {
2204 try cg.addImm32(abi_align - 1);
2205 try cg.addTag(.i32_add);
2206 try cg.addImm32(~(abi_align - 1));
2207 try cg.addTag(.i32_and);
2208 },
2209 .wasm64 => {
2210 try cg.addImm64(abi_align - 1);
2211 try cg.addTag(.i64_add);
2212 try cg.addImm64(~@as(u64, abi_align - 1));
2213 try cg.addTag(.i64_and);
2214 },
2215 }
2216 }
2217
2218 try cg.addLocal(.local_set, arg_ptr.local.value);
2219
2220 try cg.lowerToStack(operand);
2221 try cg.lowerToStack(arg_ptr);
2222 switch (cg.ptr_size) {
2223 .wasm32 => {
2224 try cg.addImm32(abi_size);
2225 try cg.addTag(.i32_add);
2226 },
2227 .wasm64 => {
2228 try cg.addImm64(abi_size);
2229 try cg.addTag(.i64_add);
2230 },
2231 }
2232 try cg.store(.stack, .stack, .usize, 0);
2233
2234 const result = if (is_f32_va_arg) result: {
2235 const promoted = try cg.load(arg_ptr, Type.f64, 0);
2236 try cg.emitWValue(promoted);
2237 try cg.addTag(.f32_demote_f64);
2238
2239 const result_local = try cg.allocLocal(Type.f32);
2240 try cg.addLocal(.local_set, result_local.local.value);
2241 break :result result_local;
2242 } else try cg.load(arg_ptr, ty, 0);
2243
2244 return cg.finishAir(inst, result, &.{ty_op.operand});
2245}
2246
21002247fn airAlloc(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
21012248 const value = try cg.allocStackPtr(inst);
21022249 return cg.finishAir(inst, value, &.{});
src/codegen/wasm/Emit.zig+1
......@@ -186,6 +186,7 @@ pub fn lowerToCode(emit: *Emit) Error!void {
186186 fn_info.cc,
187187 fn_info.param_types.get(&comp.zcu.?.intern_pool),
188188 .fromInterned(fn_info.return_type),
189 fn_info.is_var_args,
189190 target,
190191 ).?;
191192 if (is_obj) {
src/link/Wasm.zig+13-6
......@@ -932,7 +932,7 @@ pub const ZcuFunc = union {
932932 switch (ip.indexToKey(i.key(wasm).*)) {
933933 .func => |func| {
934934 const fn_info = zcu.typeToFunc(.fromInterned(func.ty)).?;
935 return wasm.getExistingFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target).?;
935 return wasm.getExistingFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), fn_info.is_var_args, target).?;
936936 },
937937 .enum_type => {
938938 return i.value(wasm).tag_name.type_index;
......@@ -2290,7 +2290,7 @@ pub const ZcuImportIndex = enum(u32) {
22902290 const nav_index = index.ptr(wasm).*;
22912291 const ext = ip.indexToKey(ip.getNav(nav_index).resolved.?.value).@"extern";
22922292 const fn_info = zcu.typeToFunc(.fromInterned(ext.ty)).?;
2293 return getExistingFunctionType(wasm, fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target).?;
2293 return getExistingFunctionType(wasm, fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), fn_info.is_var_args, target).?;
22942294 }
22952295
22962296 pub fn globalType(index: ZcuImportIndex, wasm: *const Wasm) ObjectGlobal.Type {
......@@ -3209,7 +3209,7 @@ pub fn updateFunc(
32093209 for (mir.indirect_function_set.keys()) |nav| wasm.zcu_indirect_function_set.putAssumeCapacity(nav, {});
32103210 for (mir.func_tys.keys()) |func_ty| {
32113211 const fn_info = zcu.typeToFunc(.fromInterned(func_ty)).?;
3212 _ = try wasm.internFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target);
3212 _ = try wasm.internFunctionType(fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), fn_info.is_var_args, target);
32133213 }
32143214 wasm.error_name_table_ref_count += mir.error_name_table_ref_count;
32153215 // We need to populate UAV data. In theory, we can lower the UAV values while we fill `mir.uavs`.
......@@ -3281,7 +3281,7 @@ pub fn updateNav(wasm: *Wasm, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index
32813281 wasm.function_imports.putAssumeCapacity(name, .fromZcuImport(zcu_import, wasm));
32823282 // Ensure there is a corresponding function type table entry.
32833283 const fn_info = zcu.typeToFunc(.fromInterned(ext.ty)).?;
3284 _ = try internFunctionType(wasm, fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), target);
3284 _ = try internFunctionType(wasm, fn_info.cc, fn_info.param_types.get(ip), .fromInterned(fn_info.return_type), fn_info.is_var_args, target);
32853285 } else {
32863286 wasm.data_imports.putAssumeCapacity(name, .fromZcuImport(zcu_import, wasm));
32873287 }
......@@ -3928,9 +3928,10 @@ pub fn internFunctionType(
39283928 cc: std.lang.CallingConvention,
39293929 params: []const InternPool.Index,
39303930 return_type: Zcu.Type,
3931 is_var_args: bool,
39313932 target: *const std.Target,
39323933) Allocator.Error!FunctionType.Index {
3933 try convertZcuFnType(wasm.base.comp, cc, params, return_type, target, &wasm.params_scratch, &wasm.returns_scratch);
3934 try convertZcuFnType(wasm.base.comp, cc, params, return_type, is_var_args, target, &wasm.params_scratch, &wasm.returns_scratch);
39343935 return wasm.addFuncType(.{
39353936 .params = try wasm.internValtypeList(wasm.params_scratch.items),
39363937 .returns = try wasm.internValtypeList(wasm.returns_scratch.items),
......@@ -3942,9 +3943,10 @@ pub fn getExistingFunctionType(
39423943 cc: std.lang.CallingConvention,
39433944 params: []const InternPool.Index,
39443945 return_type: Zcu.Type,
3946 is_var_args: bool,
39453947 target: *const std.Target,
39463948) ?FunctionType.Index {
3947 convertZcuFnType(wasm.base.comp, cc, params, return_type, target, &wasm.params_scratch, &wasm.returns_scratch) catch |err| switch (err) {
3949 convertZcuFnType(wasm.base.comp, cc, params, return_type, is_var_args, target, &wasm.params_scratch, &wasm.returns_scratch) catch |err| switch (err) {
39483950 error.OutOfMemory => return null,
39493951 };
39503952 return wasm.getExistingFuncType(.{
......@@ -4176,6 +4178,7 @@ fn convertZcuFnType(
41764178 cc: std.lang.CallingConvention,
41774179 params: []const InternPool.Index,
41784180 return_type: Zcu.Type,
4181 is_var_args: bool,
41794182 target: *const std.Target,
41804183 params_buffer: *std.ArrayList(std.wasm.Valtype),
41814184 returns_buffer: *std.ArrayList(std.wasm.Valtype),
......@@ -4226,6 +4229,10 @@ fn convertZcuFnType(
42264229 else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),
42274230 }
42284231 }
4232
4233 if (is_var_args) {
4234 try params_buffer.append(gpa, .i32);
4235 }
42294236}
42304237
42314238pub fn isBss(wasm: *const Wasm, optional_name: OptionalString) bool {
src/link/Wasm/Flush.zig+1-1
......@@ -151,7 +151,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
151151 const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu);
152152 gop.value_ptr.* = .{ .tag_name = .{
153153 .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, target),
154 .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .slice_const_u8_sentinel_0, false, target),
155155 .table_index = @intCast(wasm.tag_name_offs.items.len),
156156 } };
157157 const tag_names = ip.loadEnumType(data.ip_index).field_names;
test/behavior/var_args.zig-6
......@@ -94,7 +94,6 @@ fn doNothingWithFirstArg(args: anytype) void {
9494test "simple variadic function" {
9595 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
9696 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
97 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
9897 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
9998 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
10099 if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
......@@ -156,7 +155,6 @@ test "simple variadic function" {
156155test "coerce reference to var arg" {
157156 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
158157 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
159 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
160158 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
161159 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
162160 if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
......@@ -188,7 +186,6 @@ test "coerce reference to var arg" {
188186test "variadic functions" {
189187 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
190188 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
191 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
192189 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
193190 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
194191 if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
......@@ -241,7 +238,6 @@ test "variadic functions" {
241238
242239test "copy VaList" {
243240 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
244 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
245241 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
246242 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
247243 if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
......@@ -276,7 +272,6 @@ test "copy VaList" {
276272
277273test "unused VaList arg" {
278274 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
279 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
280275 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
281276 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
282277 if (builtin.zig_backend == .stage2_llvm and !builtin.os.tag.isDarwin() and builtin.cpu.arch.isAARCH64()) {
......@@ -308,7 +303,6 @@ test "unused VaList arg" {
308303
309304test "floating point VaList args" {
310305 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
311 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
312306 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
313307 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
314308 if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/16961