authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-03-21 12:18:58-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-03-23 16:29:38-04:00
loga9a91a5d49070acaa783d9d65f34a652659160c9
tree4cd4783cdaa0c5c1303dcb673ede437196fe503e
parent2f9264d8dcedba3a75dcf9d6a31b82447dfc57e8

stage2 CBE: Improve support for unions and error sets

This includes various fixes/improvements to the C backend to improve error/union support. It also fixes up our handling of decls, where some decls were not correctly marked alive.

4 files changed, 172 insertions(+), 55 deletions(-)

src/codegen/c.zig+105-35
......@@ -375,8 +375,6 @@ pub const DeclGen = struct {
375375 val: Value,
376376 decl: *Decl,
377377 ) error{ OutOfMemory, AnalysisFail }!void {
378 decl.markAlive();
379
380378 const target = dg.module.getTarget();
381379
382380 if (ty.isSlice()) {
......@@ -461,12 +459,14 @@ pub const DeclGen = struct {
461459 }
462460 }
463461
464 // Renders a "child" pointer (e.g. ElemPtr, FieldPtr) by recursing
465 // to the root decl/variable that acts as its parent
462 // Renders a "parent" pointer by recursing to the root decl/variable
463 // that its contents are defined with respect to.
466464 //
467 // Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr, since
468 // the Type of their container cannot be retrieved from their own Type
469 fn renderChildPtr(dg: *DeclGen, writer: anytype, ptr_val: Value) error{ OutOfMemory, AnalysisFail }!Type {
465 // Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr
466 fn renderParentPtr(dg: *DeclGen, writer: anytype, ptr_val: Value, ptr_ty: Type) error{ OutOfMemory, AnalysisFail }!void {
467 try writer.writeByte('(');
468 try dg.renderTypecast(writer, ptr_ty);
469 try writer.writeByte(')');
470470 switch (ptr_val.tag()) {
471471 .decl_ref_mut, .decl_ref, .variable => {
472472 const decl = switch (ptr_val.tag()) {
......@@ -475,16 +475,12 @@ pub const DeclGen = struct {
475475 .variable => ptr_val.castTag(.variable).?.data.owner_decl,
476476 else => unreachable,
477477 };
478 try dg.renderDeclName(writer, decl);
479 return decl.ty;
478 try dg.renderDeclValue(writer, ptr_ty, ptr_val, decl);
480479 },
481480 .field_ptr => {
482481 const field_ptr = ptr_val.castTag(.field_ptr).?.data;
482 const container_ty = field_ptr.container_ty;
483483 const index = field_ptr.field_index;
484
485 try writer.writeAll("&(");
486 const container_ty = try dg.renderChildPtr(writer, field_ptr.container_ptr);
487
488484 const field_name = switch (container_ty.zigTypeTag()) {
489485 .Struct => container_ty.structFields().keys()[index],
490486 .Union => container_ty.unionFields().keys()[index],
......@@ -495,19 +491,48 @@ pub const DeclGen = struct {
495491 .Union => container_ty.unionFields().values()[index].ty,
496492 else => unreachable,
497493 };
498 try writer.print(").{ }", .{fmtIdent(field_name)});
494 var container_ptr_ty_pl: Type.Payload.ElemType = .{
495 .base = .{ .tag = .c_mut_pointer },
496 .data = field_ptr.container_ty,
497 };
498 const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base);
499499
500 return field_ty;
500 if (field_ty.hasRuntimeBitsIgnoreComptime()) {
501 try writer.writeAll("&(");
502 try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty);
503 if (field_ptr.container_ty.tag() == .union_tagged) {
504 try writer.print(")->payload.{ }", .{fmtIdent(field_name)});
505 } else {
506 try writer.print(")->{ }", .{fmtIdent(field_name)});
507 }
508 } else {
509 try dg.renderParentPtr(writer, field_ptr.container_ptr, field_ty);
510 }
501511 },
502512 .elem_ptr => {
503513 const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
514 var elem_ptr_ty_pl: Type.Payload.ElemType = .{
515 .base = .{ .tag = .c_mut_pointer },
516 .data = elem_ptr.elem_ty,
517 };
518 const elem_ptr_ty = Type.initPayload(&elem_ptr_ty_pl.base);
519
504520 try writer.writeAll("&(");
505 const container_ty = try dg.renderChildPtr(writer, elem_ptr.array_ptr);
521 try dg.renderParentPtr(writer, elem_ptr.array_ptr, elem_ptr_ty);
506522 try writer.print(")[{d}]", .{elem_ptr.index});
507 return container_ty.childType();
508523 },
509 .opt_payload_ptr => return dg.fail("implement renderChildPtr for optional payload", .{}),
510 .eu_payload_ptr => return dg.fail("implement renderChildPtr for error union payload", .{}),
524 .opt_payload_ptr, .eu_payload_ptr => {
525 const payload_ptr = ptr_val.cast(Value.Payload.PayloadPtr).?.data;
526 var container_ptr_ty_pl: Type.Payload.ElemType = .{
527 .base = .{ .tag = .c_mut_pointer },
528 .data = payload_ptr.container_ty,
529 };
530 const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base);
531
532 try writer.writeAll("&(");
533 try dg.renderParentPtr(writer, payload_ptr.container_ptr, container_ptr_ty);
534 try writer.writeAll(")->payload");
535 },
511536 else => unreachable,
512537 }
513538 }
......@@ -559,6 +584,13 @@ pub const DeclGen = struct {
559584 .Int => switch (val.tag()) {
560585 .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()),
561586 .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true),
587 .field_ptr,
588 .elem_ptr,
589 .opt_payload_ptr,
590 .eu_payload_ptr,
591 .decl_ref_mut,
592 .decl_ref,
593 => try dg.renderParentPtr(writer, val, ty),
562594 else => {
563595 if (ty.isSignedInt())
564596 return writer.print("{d}", .{val.toSignedInt()});
......@@ -586,10 +618,6 @@ pub const DeclGen = struct {
586618 // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc)
587619 // between pointers and 0, which is an extension to begin with.
588620 .zero => try writer.writeByte('0'),
589 .decl_ref => {
590 const decl = val.castTag(.decl_ref).?.data;
591 return dg.renderDeclValue(writer, ty, val, decl);
592 },
593621 .variable => {
594622 const decl = val.castTag(.variable).?.data.owner_decl;
595623 return dg.renderDeclValue(writer, ty, val, decl);
......@@ -606,9 +634,6 @@ pub const DeclGen = struct {
606634 try dg.renderValue(writer, Type.usize, slice.len);
607635 try writer.writeAll("}");
608636 },
609 .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr => {
610 _ = try dg.renderChildPtr(writer, val);
611 },
612637 .function => {
613638 const func = val.castTag(.function).?.data;
614639 try dg.renderDeclName(writer, func.owner_decl);
......@@ -622,6 +647,13 @@ pub const DeclGen = struct {
622647 try dg.renderTypecast(writer, ty);
623648 try writer.print(")0x{x}u)", .{val.toUnsignedInt(target)});
624649 },
650 .field_ptr,
651 .elem_ptr,
652 .opt_payload_ptr,
653 .eu_payload_ptr,
654 .decl_ref_mut,
655 .decl_ref,
656 => try dg.renderParentPtr(writer, val, ty),
625657 else => unreachable,
626658 },
627659 .Array => {
......@@ -1326,7 +1358,7 @@ pub const DeclGen = struct {
13261358 return w.writeAll(name);
13271359 },
13281360 .Struct => {
1329 const name = dg.getTypedefName(t) orelse if (t.isTuple())
1361 const name = dg.getTypedefName(t) orelse if (t.isTuple() or t.tag() == .anon_struct)
13301362 try dg.renderTupleTypedef(t)
13311363 else
13321364 try dg.renderStructTypedef(t);
......@@ -1346,11 +1378,11 @@ pub const DeclGen = struct {
13461378
13471379 try dg.renderType(w, int_tag_ty);
13481380 },
1381 .Opaque => return w.writeAll("void"),
13491382
13501383 .Frame,
13511384 .AnyFrame,
13521385 .Vector,
1353 .Opaque,
13541386 => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{
13551387 @tagName(tag),
13561388 }),
......@@ -1497,6 +1529,8 @@ pub const DeclGen = struct {
14971529 }
14981530
14991531 fn renderDeclName(dg: DeclGen, writer: anytype, decl: *Decl) !void {
1532 decl.markAlive();
1533
15001534 if (dg.module.decl_exports.get(decl)) |exports| {
15011535 return writer.writeAll(exports[0].options.name);
15021536 } else if (decl.val.tag() == .extern_fn) {
......@@ -3188,7 +3222,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
31883222 field_name = fields.keys()[index];
31893223 field_val_ty = fields.values()[index].ty;
31903224 },
3191 .tuple => {
3225 .tuple, .anon_struct => {
31923226 const tuple = struct_ty.tupleFields();
31933227 if (tuple.values[index].tag() != .unreachable_value) return CValue.none;
31943228
......@@ -3203,9 +3237,17 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
32033237 const inst_ty = f.air.typeOfIndex(inst);
32043238 const local = try f.allocLocal(inst_ty, .Const);
32053239
3206 try writer.print(" = &", .{});
3207 try f.writeCValueDeref(writer, struct_ptr);
3208 try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) });
3240 if (field_val_ty.hasRuntimeBitsIgnoreComptime()) {
3241 try writer.writeAll(" = &");
3242 try f.writeCValueDeref(writer, struct_ptr);
3243 try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) });
3244 } else {
3245 try writer.writeAll(" = (");
3246 try f.renderTypecast(writer, inst_ty);
3247 try writer.writeByte(')');
3248 try f.writeCValue(writer, struct_ptr);
3249 try writer.writeAll(";\n");
3250 }
32093251 return local;
32103252}
32113253
......@@ -3223,7 +3265,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
32233265 const field_name = switch (struct_ty.tag()) {
32243266 .@"struct" => struct_ty.structFields().keys()[extra.field_index],
32253267 .@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index],
3226 .tuple => blk: {
3268 .tuple, .anon_struct => blk: {
32273269 const tuple = struct_ty.tupleFields();
32283270 if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none;
32293271
......@@ -3348,8 +3390,36 @@ fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue {
33483390}
33493391
33503392fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
3351 _ = inst;
3352 return f.fail("TODO: C backend: implement airErrUnionPayloadPtrSet", .{});
3393 const writer = f.object.writer();
3394 const ty_op = f.air.instructions.items(.data)[inst].ty_op;
3395 const operand = try f.resolveInst(ty_op.operand);
3396 const error_union_ty = f.air.typeOf(ty_op.operand).childType();
3397
3398 const error_ty = error_union_ty.errorUnionSet();
3399 const payload_ty = error_union_ty.errorUnionPayload();
3400
3401 // First, set the non-error value.
3402 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
3403 try f.writeCValueDeref(writer, operand);
3404 try writer.writeAll(" = ");
3405 try f.object.dg.renderValue(writer, error_ty, Value.zero);
3406 try writer.writeAll(";\n ");
3407
3408 return operand;
3409 }
3410 try f.writeCValueDeref(writer, operand);
3411 try writer.writeAll(".error = ");
3412 try f.object.dg.renderValue(writer, error_ty, Value.zero);
3413 try writer.writeAll(";\n");
3414
3415 // Then return the payload pointer (only if it is used)
3416 if (f.liveness.isUnused(inst)) return CValue.none;
3417
3418 const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const);
3419 try writer.writeAll(" = &(");
3420 try f.writeCValueDeref(writer, operand);
3421 try writer.writeAll(").payload;\n");
3422 return local;
33533423}
33543424
33553425fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
test/behavior/error.zig+12-7
......@@ -1,10 +1,20 @@
11const builtin = @import("builtin");
22const std = @import("std");
33const expect = std.testing.expect;
4const expectError = std.testing.expectError;
54const expectEqual = std.testing.expectEqual;
65const mem = std.mem;
76
7/// A more basic implementation of std.testing.expectError which
8/// does not require formatter/printing support
9fn expectError(expected_err: anyerror, observed_err_union: anytype) !void {
10 if (observed_err_union) {
11 return error.TestExpectedError;
12 } else |err| if (err == expected_err) {
13 return; // Success
14 }
15 return error.TestExpectedError;
16}
17
818test "error values" {
919 const a = @errorToInt(error.err1);
1020 const b = @errorToInt(error.err2);
......@@ -329,7 +339,6 @@ fn intLiteral(str: []const u8) !?i64 {
329339
330340test "nested error union function call in optional unwrap" {
331341 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
332 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
333342 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
334343 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
335344 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
......@@ -377,7 +386,6 @@ test "nested error union function call in optional unwrap" {
377386}
378387
379388test "return function call to error set from error union function" {
380 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
381389 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
382390 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
383391 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -410,7 +418,6 @@ test "optional error set is the same size as error set" {
410418}
411419
412420test "nested catch" {
413 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
414421 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
415422 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
416423 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -471,7 +478,6 @@ test "function pointer with return type that is error union with payload which i
471478
472479test "return result loc as peer result loc in inferred error set function" {
473480 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
474 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
475481 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
476482 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
477483 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -503,7 +509,6 @@ test "return result loc as peer result loc in inferred error set function" {
503509}
504510
505511test "error payload type is correctly resolved" {
506 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
507512 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
508513 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
509514 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
......@@ -519,7 +524,7 @@ test "error payload type is correctly resolved" {
519524 }
520525 };
521526
522 try expectEqual(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create());
527 try expect(std.meta.eql(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create()));
523528}
524529
525530test "error union comptime caching" {
test/behavior/ptrcast.zig+55-7
......@@ -39,7 +39,6 @@ fn testReinterpretWithOffsetAndNoWellDefinedLayout() !void {
3939}
4040
4141test "reinterpret bytes inside auto-layout struct as integer with nonzero offset" {
42 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
4342 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
4443 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
4544
......@@ -80,9 +79,9 @@ fn testReinterpretBytesAsExternStruct() !void {
8079 try expect(val == 5);
8180}
8281
83test "reinterpret bytes of an extern struct into another" {
82test "reinterpret bytes of an extern struct (with under-aligned fields) into another" {
8483 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
85 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
84 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO: Under-aligned fields are not yet supported in the CBE
8685 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
8786
8887 try testReinterpretExternStructAsExternStruct();
......@@ -106,12 +105,39 @@ fn testReinterpretExternStructAsExternStruct() !void {
106105 try expect(val == 5);
107106}
108107
109test "lower reinterpreted comptime field ptr" {
108test "reinterpret bytes of an extern struct into another" {
109 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
110 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
111
112 try testReinterpretOverAlignedExternStructAsExternStruct();
113 comptime try testReinterpretOverAlignedExternStructAsExternStruct();
114}
115
116fn testReinterpretOverAlignedExternStructAsExternStruct() !void {
117 const S1 = extern struct {
118 a: u32,
119 b: u32,
120 c: u8,
121 };
122 comptime var bytes: S1 = .{ .a = 0, .b = 0, .c = 5 };
123
124 const S2 = extern struct {
125 a0: u32,
126 a1: u16,
127 a2: u16,
128 c: u8,
129 };
130 var ptr = @ptrCast(*const S2, &bytes);
131 var val = ptr.c;
132 try expect(val == 5);
133}
134
135test "lower reinterpreted comptime field ptr (with under-aligned fields)" {
110136 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
111137 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
112138 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
113139 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
114 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
140 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO: CBE does not yet support under-aligned fields
115141 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
116142
117143 // Test lowering a field ptr
......@@ -131,8 +157,31 @@ test "lower reinterpreted comptime field ptr" {
131157 try expect(val2.* == 5);
132158}
133159
160test "lower reinterpreted comptime field ptr" {
161 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
162 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
163 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
164 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
165 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
166
167 // Test lowering a field ptr
168 comptime var bytes align(4) = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
169 const S = extern struct {
170 a: u32,
171 c: u8,
172 };
173 comptime var ptr = @ptrCast(*const S, &bytes);
174 var val = &ptr.c;
175 try expect(val.* == 5);
176
177 // Test lowering an elem ptr
178 comptime var src_value = S{ .a = 15, .c = 5 };
179 comptime var ptr2 = @ptrCast(*[@sizeOf(S)]u8, &src_value);
180 var val2 = &ptr2[4];
181 try expect(val2.* == 5);
182}
183
134184test "reinterpret struct field at comptime" {
135 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
136185 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
137186 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
138187
......@@ -164,7 +213,6 @@ test "comptime ptrcast keeps larger alignment" {
164213}
165214
166215test "implicit optional pointer to optional anyopaque pointer" {
167 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
168216 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
169217 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
170218
test/behavior/union.zig-6
......@@ -436,8 +436,6 @@ test "global union with single field is correctly initialized" {
436436 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
437437 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
438438
439 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
440
441439 glbl = Foo1{
442440 .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 },
443441 };
......@@ -456,8 +454,6 @@ test "initialize global array of union" {
456454 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
457455 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
458456
459 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
460
461457 glbl_array[1] = FooUnion{ .U1 = 2 };
462458 glbl_array[0] = FooUnion{ .U0 = 1 };
463459 try expect(glbl_array[0].U0 == 1);
......@@ -1033,7 +1029,6 @@ test "switching on non exhaustive union" {
10331029}
10341030
10351031test "containers with single-field enums" {
1036 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
10371032 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
10381033 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
10391034
......@@ -1113,7 +1108,6 @@ test "union enum type gets a separate scope" {
11131108}
11141109
11151110test "global variable struct contains union initialized to non-most-aligned field" {
1116 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
11171111 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
11181112 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
11191113 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO