Commit 1c8a86f063

Andrew Kelley <andrew@ziglang.org>
2022-03-03 01:28:39
Sema: detect comptime-known union initializations
Follow a similar pattern as we already do for validate_array_init and validate_struct_init. I threw in a bit of behavior test cleanup on top of it.
1 parent 220708e
src/Sema.zig
@@ -2737,6 +2737,7 @@ fn zirValidateStructInit(
             init_src,
             instrs,
             object_ptr,
+            is_comptime,
         ),
         else => unreachable,
     }
@@ -2749,6 +2750,7 @@ fn validateUnionInit(
     init_src: LazySrcLoc,
     instrs: []const Zir.Inst.Index,
     union_ptr: Air.Inst.Ref,
+    is_comptime: bool,
 ) CompileError!void {
     if (instrs.len != 1) {
         const msg = msg: {
@@ -2771,6 +2773,11 @@ fn validateUnionInit(
         return sema.failWithOwnedErrorMsg(msg);
     }
 
+    if (is_comptime or block.is_comptime) {
+        // In this case, comptime machinery already did everything. No work to do here.
+        return;
+    }
+
     const field_ptr = instrs[0];
     const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
     const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };
@@ -2779,44 +2786,72 @@ fn validateUnionInit(
     const field_index_big = union_obj.fields.getIndex(field_name) orelse
         return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
     const field_index = @intCast(u32, field_index_big);
-
-    // Handle the possibility of the union value being comptime-known.
-    const union_ptr_inst = Air.refToIndex(union_ptr).?;
-    switch (sema.air_instructions.items(.tag)[union_ptr_inst]) {
-        .constant => {
-            if (try sema.resolveDefinedValue(block, init_src, union_ptr)) |ptr_val| {
-                if (ptr_val.isComptimeMutablePtr()) {
-                    // In this case the tag has already been set. No validation to do.
-                    return;
-                }
-            }
-        },
-        .bitcast => {
-            // TODO here we need to go back and see if we need to convert the union
-            // to a comptime-known value. In such case, we must delete all the instructions
-            // added to the current block starting with the bitcast.
-            // If the bitcast result ptr is an alloc, the alloc should be replaced with
-            // a constant decl_ref.
-            // Otherwise, the bitcast should be preserved and a store instruction should be
-            // emitted to store the constant union value through the bitcast.
-        },
-        .alloc => {},
-        else => |t| {
-            if (std.debug.runtime_safety) {
-                std.debug.panic("unexpected AIR tag for union pointer: {s}", .{@tagName(t)});
-            } else {
-                unreachable;
-            }
-        },
+    const air_tags = sema.air_instructions.items(.tag);
+    const air_datas = sema.air_instructions.items(.data);
+    const field_ptr_air_ref = sema.inst_map.get(field_ptr).?;
+    const field_ptr_air_inst = Air.refToIndex(field_ptr_air_ref).?;
+
+    // Our task here is to determine if the union is comptime-known. In such case,
+    // we erase the runtime AIR instructions for initializing the union, and replace
+    // the mapping with the comptime value. Either way, we will need to populate the tag.
+
+    // We expect to see something like this in the current block AIR:
+    //   %a = alloc(*const U)
+    //   %b = bitcast(*U, %a)
+    //   %c = field_ptr(..., %b)
+    //   %e!= store(%c!, %d!)
+    // If %d is a comptime operand, the union is comptime.
+    // If the union is comptime, we want `first_block_index`
+    // to point at %c so that the bitcast becomes the last instruction in the block.
+    //
+    // In the case of a comptime-known pointer to a union, the
+    // the field_ptr instruction is missing, so we have to pattern-match
+    // based only on the store instructions.
+    // `first_block_index` needs to point to the `field_ptr` if it exists;
+    // the `store` otherwise.
+    //
+    // It's also possible for there to be no store instruction, in the case
+    // of nested `coerce_result_ptr` instructions. If we see the `field_ptr`
+    // but we have not found a `store`, treat as a runtime-known field.
+    var first_block_index = block.instructions.items.len;
+    var block_index = block.instructions.items.len - 1;
+    var init_val: ?Value = null;
+    while (block_index > 0) : (block_index -= 1) {
+        const store_inst = block.instructions.items[block_index];
+        if (store_inst == field_ptr_air_inst) break;
+        if (air_tags[store_inst] != .store) continue;
+        const bin_op = air_datas[store_inst].bin_op;
+        if (bin_op.lhs != field_ptr_air_ref) continue;
+        if (block_index > 0 and
+            field_ptr_air_inst == block.instructions.items[block_index - 1])
+        {
+            first_block_index = @minimum(first_block_index, block_index - 1);
+        } else {
+            first_block_index = @minimum(first_block_index, block_index);
+        }
+        init_val = try sema.resolveMaybeUndefValAllowVariables(block, init_src, bin_op.rhs);
+        break;
     }
 
-    // Otherwise, we set the new union tag now.
-    const new_tag = try sema.addConstant(
-        union_obj.tag_ty,
-        try Value.Tag.enum_field_index.create(sema.arena, field_index),
-    );
+    const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index);
+
+    if (init_val) |val| {
+        // Our task is to delete all the `field_ptr` and `store` instructions, and insert
+        // instead a single `store` to the result ptr with a comptime union value.
+        block.instructions.shrinkRetainingCapacity(first_block_index);
+
+        const union_val = try Value.Tag.@"union".create(sema.arena, .{
+            .tag = tag_val,
+            .val = val,
+        });
+        const union_ty = sema.typeOf(union_ptr).childType();
+        const union_init = try sema.addConstant(union_ty, union_val);
+        try sema.storePtr2(block, init_src, union_ptr, init_src, union_init, init_src, .store);
+        return;
+    }
 
     try sema.requireRuntimeBlock(block, init_src);
+    const new_tag = try sema.addConstant(union_obj.tag_ty, tag_val);
     _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag);
 }
 
@@ -3084,7 +3119,7 @@ fn zirValidateArrayInit(
     }
 
     var array_is_comptime = true;
-    var first_block_index: usize = std.math.maxInt(u32);
+    var first_block_index = block.instructions.items.len;
 
     // Collect the comptime element values in case the array literal ends up
     // being comptime-known.
@@ -15147,7 +15182,32 @@ fn unionFieldPtr(
     });
 
     if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| {
-        // TODO detect inactive union field and emit compile error
+        switch (union_obj.layout) {
+            .Auto => {
+                // TODO emit the access of inactive union field error commented out below.
+                // In order to do that, we need to first solve the problem that AstGen
+                // emits field_ptr instructions in order to initialize union values.
+                // In such case we need to know that the field_ptr instruction (which is
+                // calling this unionFieldPtr function) is *initializing* the union,
+                // in which case we would skip this check, and in fact we would actually
+                // set the union tag here and the payload to undefined.
+
+                //const tag_and_val = union_val.castTag(.@"union").?.data;
+                //var field_tag_buf: Value.Payload.U32 = .{
+                //    .base = .{ .tag = .enum_field_index },
+                //    .data = field_index,
+                //};
+                //const field_tag = Value.initPayload(&field_tag_buf.base);
+                //const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty);
+                //if (!tag_matches) {
+                //    // TODO enhance this saying which one was active
+                //    // and which one was accessed, and showing where the union was declared.
+                //    return sema.fail(block, src, "access of inactive union field", .{});
+                //}
+                // TODO add runtime safety check for the active tag
+            },
+            .Packed, .Extern => {},
+        }
         return sema.addConstant(
             ptr_field_ty,
             try Value.Tag.field_ptr.create(arena, .{
@@ -15183,9 +15243,33 @@ fn unionFieldVal(
     if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| {
         if (union_val.isUndef()) return sema.addConstUndef(field.ty);
 
-        // TODO detect inactive union field and emit compile error
-        const active_val = union_val.castTag(.@"union").?.data.val;
-        return sema.addConstant(field.ty, active_val);
+        const tag_and_val = union_val.castTag(.@"union").?.data;
+        var field_tag_buf: Value.Payload.U32 = .{
+            .base = .{ .tag = .enum_field_index },
+            .data = field_index,
+        };
+        const field_tag = Value.initPayload(&field_tag_buf.base);
+        const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty);
+        switch (union_obj.layout) {
+            .Auto => {
+                if (tag_matches) {
+                    return sema.addConstant(field.ty, tag_and_val.val);
+                } else {
+                    // TODO enhance this saying which one was active
+                    // and which one was accessed, and showing where the union was declared.
+                    return sema.fail(block, src, "access of inactive union field", .{});
+                }
+            },
+            .Packed, .Extern => {
+                if (tag_matches) {
+                    return sema.addConstant(field.ty, tag_and_val.val);
+                } else {
+                    const old_ty = union_ty.unionFieldType(tag_and_val.tag);
+                    const new_val = try sema.bitCastVal(block, src, tag_and_val.val, old_ty, field.ty);
+                    return sema.addConstant(field.ty, new_val);
+                }
+            },
+        }
     }
 
     try sema.requireRuntimeBlock(block, src);
test/behavior/array.zig
@@ -108,15 +108,19 @@ test "array len field" {
 }
 
 test "array with sentinels" {
+    if (builtin.zig_backend == .stage1) {
+        // Stage1 test coverage disabled at runtime because of
+        // https://github.com/ziglang/zig/issues/4372
+        return error.SkipZigTest;
+    }
+
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest(is_ct: bool) !void {
-            if (is_ct or builtin.zig_backend != .stage1) {
+            {
                 var zero_sized: [0:0xde]u8 = [_:0xde]u8{};
-                // Stage1 test coverage disabled at runtime because of
-                // https://github.com/ziglang/zig/issues/4372
                 try expect(zero_sized[0] == 0xde);
                 var reinterpreted = @ptrCast(*[1]u8, &zero_sized);
                 try expect(reinterpreted[0] == 0xde);
test/behavior/cast.zig
@@ -1050,9 +1050,9 @@ fn incrementVoidPtrArray(array: ?*anyopaque, len: usize) void {
 }
 
 test "compile time int to ptr of function" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) return error.SkipZigTest; // TODO
+
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
@@ -1060,11 +1060,13 @@ test "compile time int to ptr of function" {
     try foobar(FUNCTION_CONSTANT);
 }
 
-pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize));
+// On some architectures function pointers must be aligned.
+const hardcoded_fn_addr = maxInt(usize) & ~@as(usize, 0xf);
+pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, hardcoded_fn_addr);
 pub const PFN_void = *const fn (*anyopaque) callconv(.C) void;
 
 fn foobar(func: PFN_void) !void {
-    try std.testing.expect(@ptrToInt(func) == maxInt(usize));
+    try std.testing.expect(@ptrToInt(func) == hardcoded_fn_addr);
 }
 
 test "implicit ptr to *anyopaque" {
test/behavior/floatop.zig
@@ -21,6 +21,12 @@ fn epsForType(comptime T: type) T {
 }
 
 test "floating point comparisons" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testFloatComparisons();
     comptime try testFloatComparisons();
 }
@@ -51,6 +57,12 @@ fn testFloatComparisons() !void {
 }
 
 test "different sized float comparisons" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testDifferentSizedFloatComparisons();
     comptime try testDifferentSizedFloatComparisons();
 }
@@ -81,12 +93,24 @@ fn testDifferentSizedFloatComparisons() !void {
 //}
 
 test "negative f128 floatToInt at compile-time" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const a: f128 = -2;
     var b = @floatToInt(i64, a);
     try expect(@as(i64, -2) == b);
 }
 
 test "@sqrt" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testSqrt();
     try testSqrt();
 }
@@ -129,6 +153,12 @@ fn testSqrt() !void {
 }
 
 test "more @sqrt f16 tests" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     // TODO these are not all passing at comptime
     try expect(@sqrt(@as(f16, 0.0)) == 0.0);
     try expect(math.approxEqAbs(f16, @sqrt(@as(f16, 2.0)), 1.414214, epsilon));
@@ -149,14 +179,21 @@ test "more @sqrt f16 tests" {
 }
 
 test "@sin" {
+    if (builtin.zig_backend == .stage1) {
+        // stage1 emits an incorrect compile error for `@as(ty, std.math.pi / 2)`
+        return error.SkipZigTest;
+    }
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testSin();
     try testSin();
 }
 
 fn testSin() !void {
-    // stage1 emits an incorrect compile error for `@as(ty, std.math.pi / 2)`
-    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-
     inline for ([_]type{ f16, f32, f64 }) |ty| {
         const eps = epsForType(ty);
         try expect(@sin(@as(ty, 0)) == 0);
@@ -176,14 +213,21 @@ fn testSin() !void {
 }
 
 test "@cos" {
+    if (builtin.zig_backend == .stage1) {
+        // stage1 emits an incorrect compile error for `@as(ty, std.math.pi / 2)`
+        return error.SkipZigTest;
+    }
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testCos();
     try testCos();
 }
 
 fn testCos() !void {
-    // stage1 emits an incorrect compile error for `@as(ty, std.math.pi / 2)`
-    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-
     inline for ([_]type{ f16, f32, f64 }) |ty| {
         const eps = epsForType(ty);
         try expect(@cos(@as(ty, 0)) == 1);
@@ -203,6 +247,12 @@ fn testCos() !void {
 }
 
 test "@exp" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testExp();
     try testExp();
 }
@@ -226,6 +276,12 @@ fn testExp() !void {
 }
 
 test "@exp2" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testExp2();
     try testExp2();
 }
@@ -249,7 +305,7 @@ fn testExp2() !void {
 }
 
 test "@log" {
-    if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     comptime try testLog();
     try testLog();
@@ -285,7 +341,7 @@ fn testLog() !void {
 }
 
 test "@log2" {
-    if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     comptime try testLog2();
     try testLog2();
@@ -310,7 +366,7 @@ fn testLog2() !void {
 }
 
 test "@log10" {
-    if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     comptime try testLog10();
     try testLog10();
@@ -335,6 +391,12 @@ fn testLog10() !void {
 }
 
 test "@fabs" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testFabs();
     try testFabs();
 }
@@ -367,6 +429,12 @@ fn testFabs() !void {
 }
 
 test "@floor" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testFloor();
     try testFloor();
 }
@@ -394,6 +462,12 @@ fn testFloor() !void {
 }
 
 test "@ceil" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testCeil();
     try testCeil();
 }
@@ -421,6 +495,12 @@ fn testCeil() !void {
 }
 
 test "@trunc" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     comptime try testTrunc();
     try testTrunc();
 }
@@ -449,7 +529,11 @@ fn testTrunc() !void {
 
 test "negation" {
     if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-    if (builtin.os.tag == .freebsd) return error.SkipZigTest;
+
+    if (builtin.os.tag == .freebsd) {
+        // TODO file issue to track this failure
+        return error.SkipZigTest;
+    }
 
     const S = struct {
         fn doTheTest() !void {
test/behavior/inttoptr.zig
@@ -2,7 +2,6 @@ const builtin = @import("builtin");
 
 test "casting integer address to function pointer" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
 
@@ -11,7 +10,7 @@ test "casting integer address to function pointer" {
 }
 
 fn addressToFunction() void {
-    var addr: usize = 0xdeadbeef;
+    var addr: usize = 0xdeadbee0;
     _ = @intToPtr(*const fn () void, addr);
 }
 
test/behavior/struct.zig
@@ -1175,7 +1175,11 @@ test "for loop over pointers to struct, getting field from struct pointer" {
 
 test "anon init through error unions and optionals" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     const S = struct {
         a: u32,
@@ -1200,7 +1204,11 @@ test "anon init through error unions and optionals" {
 
 test "anon init through optional" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     const S = struct {
         a: u32,
@@ -1218,7 +1226,11 @@ test "anon init through optional" {
 
 test "anon init through error union" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     const S = struct {
         a: u32,
@@ -1236,7 +1248,11 @@ test "anon init through error union" {
 
 test "typed init through error unions and optionals" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
-    if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     const S = struct {
         a: u32,
test/behavior/switch.zig
@@ -1,9 +1,13 @@
+const builtin = @import("builtin");
 const std = @import("std");
 const expect = std.testing.expect;
 const expectError = std.testing.expectError;
 const expectEqual = std.testing.expectEqual;
 
 test "switch with numbers" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testSwitchWithNumbers(13);
 }
 
@@ -17,6 +21,9 @@ fn testSwitchWithNumbers(x: u32) !void {
 }
 
 test "switch with all ranges" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try expect(testSwitchWithAllRanges(50, 3) == 1);
     try expect(testSwitchWithAllRanges(101, 0) == 2);
     try expect(testSwitchWithAllRanges(300, 5) == 3);
@@ -48,6 +55,9 @@ test "implicit comptime switch" {
 }
 
 test "switch on enum" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const fruit = Fruit.Orange;
     nonConstSwitchOnEnum(fruit);
 }
@@ -65,6 +75,9 @@ fn nonConstSwitchOnEnum(fruit: Fruit) void {
 }
 
 test "switch statement" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try nonConstSwitch(SwitchStatementFoo.C);
 }
 fn nonConstSwitch(foo: SwitchStatementFoo) !void {
@@ -79,6 +92,10 @@ fn nonConstSwitch(foo: SwitchStatementFoo) !void {
 const SwitchStatementFoo = enum { A, B, C, D };
 
 test "switch with multiple expressions" {
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const x = switch (returnsFive()) {
         1, 2, 3 => 1,
         4, 5, 6 => 2,
@@ -91,6 +108,9 @@ fn returnsFive() i32 {
 }
 
 test "switch on type" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try expect(trueIfBoolFalseOtherwise(bool));
     try expect(!trueIfBoolFalseOtherwise(i32));
 }
@@ -103,6 +123,10 @@ fn trueIfBoolFalseOtherwise(comptime T: type) bool {
 }
 
 test "switching on booleans" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testSwitchOnBools();
     comptime try testSwitchOnBools();
 }
@@ -154,6 +178,9 @@ test "undefined.u0" {
 }
 
 test "switch with disjoint range" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     var q: u8 = 0;
     switch (q) {
         0...125 => {},
@@ -163,6 +190,9 @@ test "switch with disjoint range" {
 }
 
 test "switch variable for range and multiple prongs" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const S = struct {
         fn doTheTest() !void {
             var u: u8 = 16;
@@ -196,6 +226,9 @@ fn poll() void {
 }
 
 test "switch on global mutable var isn't constant-folded" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     while (state < 2) {
         poll();
     }
@@ -208,6 +241,10 @@ const SwitchProngWithVarEnum = union(enum) {
 };
 
 test "switch prong with variable" {
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
     try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
     try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
@@ -228,6 +265,9 @@ fn switchProngWithVarFn(a: SwitchProngWithVarEnum) !void {
 }
 
 test "switch on enum using pointer capture" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testSwitchEnumPtrCapture();
     comptime try testSwitchEnumPtrCapture();
 }
@@ -245,6 +285,10 @@ fn testSwitchEnumPtrCapture() !void {
 }
 
 test "switch handles all cases of number" {
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testSwitchHandleAllCases();
     comptime try testSwitchHandleAllCases();
 }
@@ -282,6 +326,9 @@ fn testSwitchHandleAllCasesRange(x: u8) u8 {
 }
 
 test "switch on union with some prongs capturing" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const X = union(enum) {
         a,
         b: i32,
@@ -311,10 +358,16 @@ fn returnsFalse() bool {
     }
 }
 test "switch on const enum with var" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try expect(!returnsFalse());
 }
 
 test "anon enum literal used in switch on union enum" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const Foo = union(enum) {
         a: i32,
     };
@@ -328,6 +381,9 @@ test "anon enum literal used in switch on union enum" {
 }
 
 test "switch all prongs unreachable" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     try testAllProngsUnreachable();
     comptime try testAllProngsUnreachable();
 }
@@ -349,6 +405,9 @@ fn switchWithUnreachable(x: i32) i32 {
 }
 
 test "capture value of switch with all unreachable prongs" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const x = return_a_number() catch |err| switch (err) {
         else => unreachable,
     };
@@ -360,6 +419,10 @@ fn return_a_number() anyerror!i32 {
 }
 
 test "switch on integer with else capturing expr" {
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const S = struct {
         fn doTheTest() !void {
             var x: i32 = 5;
@@ -375,7 +438,7 @@ test "switch on integer with else capturing expr" {
 }
 
 test "else prong of switch on error set excludes other cases" {
-    if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn doTheTest() !void {
@@ -407,7 +470,7 @@ test "else prong of switch on error set excludes other cases" {
 }
 
 test "switch prongs with error set cases make a new error set type for capture value" {
-    if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn doTheTest() !void {
@@ -441,6 +504,9 @@ test "switch prongs with error set cases make a new error set type for capture v
 }
 
 test "return result loc and then switch with range implicit casted to error union" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const S = struct {
         fn doTheTest() !void {
             try expect((func(0xb) catch unreachable) == 0xb);
@@ -457,6 +523,10 @@ test "return result loc and then switch with range implicit casted to error unio
 }
 
 test "switch with null and T peer types and inferred result location type" {
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
     const S = struct {
         fn doTheTest(c: u8) !void {
             if (switch (c) {
@@ -473,7 +543,7 @@ test "switch with null and T peer types and inferred result location type" {
 }
 
 test "switch prongs with cases with identical payload types" {
-    if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     const Union = union(enum) {
         A: usize,
@@ -515,7 +585,11 @@ test "switch prongs with cases with identical payload types" {
 }
 
 test "switch on pointer type" {
-    if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
 
     const S = struct {
         const X = struct {
@@ -544,7 +618,7 @@ test "switch on pointer type" {
 }
 
 test "switch on error set with single else" {
-    if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn doTheTest() !void {
@@ -563,7 +637,7 @@ test "switch on error set with single else" {
 }
 
 test "switch capture copies its payload" {
-    if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     const S = struct {
         fn doTheTest() !void {
test/behavior/union.zig
@@ -878,8 +878,6 @@ test "union with comptime_int tag" {
 }
 
 test "extern union doesn't trigger field check at comptime" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const U = extern union {
         x: u32,
         y: u8,
@@ -890,7 +888,8 @@ test "extern union doesn't trigger field check at comptime" {
 }
 
 test "anonymous union literal syntax" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
     const S = struct {
         const Number = union {
@@ -914,7 +913,8 @@ test "anonymous union literal syntax" {
 }
 
 test "function call result coerces from tagged union to the tag" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
     const S = struct {
         const Arch = union(enum) {
@@ -1104,9 +1104,9 @@ test "union enum type gets a separate scope" {
 test "global variable struct contains union initialized to non-most-aligned field" {
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
     const T = struct {
         const U = union(enum) {
test/behavior.zig
@@ -51,6 +51,7 @@ test {
     _ = @import("behavior/defer.zig");
     _ = @import("behavior/enum.zig");
     _ = @import("behavior/error.zig");
+    _ = @import("behavior/floatop.zig");
     _ = @import("behavior/fn.zig");
     _ = @import("behavior/fn_delegation.zig");
     _ = @import("behavior/fn_in_struct_in_comptime.zig");
@@ -79,6 +80,7 @@ test {
     _ = @import("behavior/slice_sentinel_comptime.zig");
     _ = @import("behavior/src.zig");
     _ = @import("behavior/struct.zig");
+    _ = @import("behavior/switch.zig");
     _ = @import("behavior/this.zig");
     _ = @import("behavior/truncate.zig");
     _ = @import("behavior/try.zig");
@@ -93,7 +95,6 @@ test {
     _ = @import("behavior/void.zig");
     _ = @import("behavior/while.zig");
 
-    // tests that don't pass for stage1
     if (builtin.zig_backend != .stage1) {
         _ = @import("behavior/decltest.zig");
     }
@@ -116,13 +117,11 @@ test {
         if (builtin.zig_backend != .stage2_c) {
             // Tests that pass for stage1 and the llvm backend.
             _ = @import("behavior/atomics.zig");
-            _ = @import("behavior/floatop.zig");
             _ = @import("behavior/math.zig");
             _ = @import("behavior/maximum_minimum.zig");
             _ = @import("behavior/popcount.zig");
             _ = @import("behavior/saturating_arithmetic.zig");
             _ = @import("behavior/sizeof_and_typeof.zig");
-            _ = @import("behavior/switch.zig");
             _ = @import("behavior/widening.zig");
             _ = @import("behavior/bugs/421.zig");
             _ = @import("behavior/bugs/726.zig");