Commit e5d5a8bc4e

Jacob Young <jacobly0@users.noreply.github.com>
2025-01-02 09:10:19
x86_64: implement switch jump tables
1 parent ac1a975
lib/std/debug/SelfInfo.zig
@@ -121,13 +121,13 @@ pub fn deinit(self: *SelfInfo) void {
 }
 
 pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
-    if (comptime builtin.target.isDarwin()) {
+    if (builtin.target.isDarwin()) {
         return self.lookupModuleDyld(address);
     } else if (native_os == .windows) {
         return self.lookupModuleWin32(address);
     } else if (native_os == .haiku) {
         return self.lookupModuleHaiku(address);
-    } else if (comptime builtin.target.isWasm()) {
+    } else if (builtin.target.isWasm()) {
         return self.lookupModuleWasm(address);
     } else {
         return self.lookupModuleDl(address);
@@ -138,13 +138,13 @@ pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
 // This can be called when getModuleForAddress fails, so implementations should provide
 // a path that doesn't rely on any side-effects of a prior successful module lookup.
 pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 {
-    if (comptime builtin.target.isDarwin()) {
+    if (builtin.target.isDarwin()) {
         return self.lookupModuleNameDyld(address);
     } else if (native_os == .windows) {
         return self.lookupModuleNameWin32(address);
     } else if (native_os == .haiku) {
         return null;
-    } else if (comptime builtin.target.isWasm()) {
+    } else if (builtin.target.isWasm()) {
         return null;
     } else {
         return self.lookupModuleNameDl(address);
lib/std/math/big/int.zig
@@ -2523,7 +2523,7 @@ pub const Const = struct {
     /// Returns the number of leading zeros in twos-complement form.
     pub fn clz(a: Const, bits: Limb) Limb {
         // Limbs are stored in little-endian order but we need to iterate big-endian.
-        if (!a.positive) return 0;
+        if (!a.positive and !a.eqlZero()) return 0;
         var total_limb_lz: Limb = 0;
         var i: usize = a.limbs.len;
         const bits_per_limb = @bitSizeOf(Limb);
lib/std/os/windows.zig
@@ -1061,7 +1061,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
     // us INVALID_PARAMETER.
     // The same reasoning for win10_rs5 as in os.renameatW() applies (FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5).
     var need_fallback = true;
-    if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) {
+    if (builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) {
         // Deletion with posix semantics if the filesystem supports it.
         var info = FILE_DISPOSITION_INFORMATION_EX{
             .Flags = FILE_DISPOSITION_DELETE |
lib/std/posix/test.zig
@@ -804,7 +804,7 @@ test "getrlimit and setrlimit" {
         //
         // This happens for example if RLIMIT_MEMLOCK is bigger than ~2GiB.
         // In that case the following the limit would be RLIM_INFINITY and the following setrlimit fails with EPERM.
-        if (comptime builtin.cpu.arch.isMIPS() and builtin.link_libc) {
+        if (builtin.cpu.arch.isMIPS() and builtin.link_libc) {
             if (limit.cur != linux.RLIM.INFINITY) {
                 try posix.setrlimit(resource, limit);
             }
lib/std/Thread/Condition.zig
@@ -161,17 +161,17 @@ const WindowsImpl = struct {
             }
         }
 
-        if (comptime builtin.mode == .Debug) {
+        if (builtin.mode == .Debug) {
             // The internal state of the DebugMutex needs to be handled here as well.
             mutex.impl.locking_thread.store(0, .unordered);
         }
         const rc = os.windows.kernel32.SleepConditionVariableSRW(
             &self.condition,
-            if (comptime builtin.mode == .Debug) &mutex.impl.impl.srwlock else &mutex.impl.srwlock,
+            if (builtin.mode == .Debug) &mutex.impl.impl.srwlock else &mutex.impl.srwlock,
             timeout_ms,
             0, // the srwlock was assumed to acquired in exclusive mode not shared
         );
-        if (comptime builtin.mode == .Debug) {
+        if (builtin.mode == .Debug) {
             // The internal state of the DebugMutex needs to be handled here as well.
             mutex.impl.locking_thread.store(std.Thread.getCurrentId(), .unordered);
         }
lib/std/Thread/Mutex.zig
@@ -158,7 +158,7 @@ const FutexImpl = struct {
         // On x86, use `lock bts` instead of `lock cmpxchg` as:
         // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
         // - `lock bts` is smaller instruction-wise which makes it better for inlining
-        if (comptime builtin.target.cpu.arch.isX86()) {
+        if (builtin.target.cpu.arch.isX86()) {
             const locked_bit = @ctz(locked);
             return self.state.bitSet(locked_bit, .acquire) == 0;
         }
lib/std/zig/system/NativePaths.zig
@@ -83,7 +83,7 @@ pub fn detect(arena: Allocator, native_target: std.Target) !NativePaths {
 
     // TODO: consider also adding homebrew paths
     // TODO: consider also adding macports paths
-    if (comptime builtin.target.isDarwin()) {
+    if (builtin.target.isDarwin()) {
         if (std.zig.system.darwin.isSdkInstalled(arena)) sdk: {
             const sdk = std.zig.system.darwin.getSdk(arena, native_target) orelse break :sdk;
             try self.addLibDir(try std.fs.path.join(arena, &.{ sdk, "usr/lib" }));
lib/std/debug.zig
@@ -179,7 +179,7 @@ pub fn dumpHexFallible(bytes: []const u8) !void {
 /// TODO multithreaded awareness
 pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
     nosuspend {
-        if (comptime builtin.target.isWasm()) {
+        if (builtin.target.isWasm()) {
             if (native_os == .wasi) {
                 const stderr = io.getStdErr().writer();
                 stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
@@ -267,7 +267,7 @@ pub inline fn getContext(context: *ThreadContext) bool {
 /// TODO multithreaded awareness
 pub fn dumpStackTraceFromBase(context: *ThreadContext) void {
     nosuspend {
-        if (comptime builtin.target.isWasm()) {
+        if (builtin.target.isWasm()) {
             if (native_os == .wasi) {
                 const stderr = io.getStdErr().writer();
                 stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
@@ -365,7 +365,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
 /// TODO multithreaded awareness
 pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void {
     nosuspend {
-        if (comptime builtin.target.isWasm()) {
+        if (builtin.target.isWasm()) {
             if (native_os == .wasi) {
                 const stderr = io.getStdErr().writer();
                 stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
lib/std/heap.zig
@@ -890,7 +890,7 @@ test {
     _ = @import("heap/memory_pool.zig");
     _ = ArenaAllocator;
     _ = GeneralPurposeAllocator;
-    if (comptime builtin.target.isWasm()) {
+    if (builtin.target.isWasm()) {
         _ = WasmAllocator;
         _ = WasmPageAllocator;
     }
lib/std/os.zig
@@ -157,7 +157,7 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
             return target;
         },
         .freebsd => {
-            if (comptime builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) {
+            if (builtin.os.isAtLeast(.freebsd, .{ .major = 13, .minor = 0, .patch = 0 }) orelse false) {
                 var kfile: std.c.kinfo_file = undefined;
                 kfile.structsize = std.c.KINFO_FILE_SIZE;
                 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
lib/std/posix.zig
@@ -6819,7 +6819,7 @@ pub fn memfd_createZ(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t {
             }
         },
         .freebsd => {
-            if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
+            if (builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
                 @compileError("memfd_create is unavailable on FreeBSD < 13.0");
             const rc = system.memfd_create(name, flags);
             switch (errno(rc)) {
lib/std/simd.zig
@@ -163,7 +163,7 @@ pub fn interlace(vecs: anytype) @Vector(vectorLength(@TypeOf(vecs[0])) * vecs.le
     //  The indices are correct. The problem seems to be with the @shuffle builtin.
     //  On MIPS, the test that interlaces small_base gives { 0, 2, 0, 0, 64, 255, 248, 200, 0, 0 }.
     //  Calling this with two inputs seems to work fine, but I'll let the compile error trigger for all inputs, just to be safe.
-    comptime if (builtin.cpu.arch.isMIPS()) @compileError("TODO: Find out why interlace() doesn't work on MIPS");
+    if (builtin.cpu.arch.isMIPS()) @compileError("TODO: Find out why interlace() doesn't work on MIPS");
 
     const VecType = @TypeOf(vecs[0]);
     const vecs_arr = @as([vecs.len]VecType, vecs);
@@ -248,7 +248,7 @@ test "vector patterns" {
     try std.testing.expectEqual([8]u32{ 10, 20, 30, 40, 55, 66, 77, 88 }, join(base, other_base));
     try std.testing.expectEqual([2]u32{ 20, 30 }, extract(base, 1, 2));
 
-    if (comptime !builtin.cpu.arch.isMIPS()) {
+    if (!builtin.cpu.arch.isMIPS()) {
         try std.testing.expectEqual([8]u32{ 10, 55, 20, 66, 30, 77, 40, 88 }, interlace(.{ base, other_base }));
 
         const small_braid = interlace(small_bases);
@@ -390,7 +390,7 @@ pub fn prefixScanWithFunc(
     comptime identity: std.meta.Child(@TypeOf(vec)),
 ) if (ErrorType == void) @TypeOf(vec) else ErrorType!@TypeOf(vec) {
     // I haven't debugged this, but it might be a cousin of sorts to what's going on with interlace.
-    comptime if (builtin.cpu.arch.isMIPS()) @compileError("TODO: Find out why prefixScan doesn't work on MIPS");
+    if (builtin.cpu.arch.isMIPS()) @compileError("TODO: Find out why prefixScan doesn't work on MIPS");
 
     const len = vectorLength(@TypeOf(vec));
 
@@ -465,9 +465,7 @@ test "vector prefix scan" {
     if ((builtin.cpu.arch == .armeb or builtin.cpu.arch == .thumbeb) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22060
     if (builtin.cpu.arch == .aarch64_be and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21893
 
-    if (comptime builtin.cpu.arch.isMIPS()) {
-        return error.SkipZigTest;
-    }
+    if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest;
 
     const int_base = @Vector(4, i32){ 11, 23, 9, -21 };
     const float_base = @Vector(4, f32){ 2, 0.5, -10, 6.54321 };
src/arch/x86_64/bits.zig
@@ -482,17 +482,18 @@ pub const Memory = struct {
     base: Base = .none,
     mod: Mod = .{ .rm = .{} },
 
-    pub const Base = union(enum(u2)) {
+    pub const Base = union(enum(u3)) {
         none,
         reg: Register,
         frame: FrameIndex,
+        table,
         reloc: u32,
 
         pub const Tag = @typeInfo(Base).@"union".tag_type.?;
 
         pub fn isExtended(self: Base) bool {
             return switch (self) {
-                .none, .frame, .reloc => false, // rsp, rbp, and rip are not extended
+                .none, .frame, .table, .reloc => false, // rsp, rbp, and rip are not extended
                 .reg => |reg| reg.isExtended(),
             };
         }
src/arch/x86_64/CodeGen.zig
@@ -61,9 +61,10 @@ src_loc: Zcu.LazySrcLoc,
 eflags_inst: ?Air.Inst.Index = null,
 
 /// MIR Instructions
-mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
+mir_instructions: std.MultiArrayList(Mir.Inst) = .empty,
 /// MIR extra data
 mir_extra: std.ArrayListUnmanaged(u32) = .empty,
+mir_table: std.ArrayListUnmanaged(Mir.Inst.Index) = .empty,
 
 /// Byte offset within the source file of the ending curly.
 end_di_line: u32,
@@ -75,8 +76,8 @@ end_di_column: u32,
 exitlude_jump_relocs: std.ArrayListUnmanaged(Mir.Inst.Index) = .empty,
 
 reused_operands: std.StaticBitSet(Liveness.bpi - 1) = undefined,
-const_tracking: ConstTrackingMap = .{},
-inst_tracking: InstTrackingMap = .{},
+const_tracking: ConstTrackingMap = .empty,
+inst_tracking: InstTrackingMap = .empty,
 
 // Key is the block instruction
 blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .empty,
@@ -86,16 +87,26 @@ register_manager: RegisterManager = .{},
 /// Generation of the current scope, increments by 1 for every entered scope.
 scope_generation: u32 = 0,
 
-frame_allocs: std.MultiArrayList(FrameAlloc) = .{},
+frame_allocs: std.MultiArrayList(FrameAlloc) = .empty,
 free_frame_indices: std.AutoArrayHashMapUnmanaged(FrameIndex, void) = .empty,
-frame_locs: std.MultiArrayList(Mir.FrameLoc) = .{},
+frame_locs: std.MultiArrayList(Mir.FrameLoc) = .empty,
 
 loops: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
     /// The state to restore before branching.
     state: State,
     /// The branch target.
     target: Mir.Inst.Index,
-}) = .{},
+}) = .empty,
+loop_switches: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
+    start: u31,
+    len: u11,
+    min: Value,
+    else_relocs: union(enum) {
+        @"unreachable",
+        forward: std.ArrayListUnmanaged(Mir.Inst.Index),
+        backward: Mir.Inst.Index,
+    },
+}) = .empty,
 
 next_temp_index: Temp.Index = @enumFromInt(0),
 temp_type: [Temp.Index.max]Type = undefined,
@@ -904,6 +915,7 @@ pub fn generate(
         function.free_frame_indices.deinit(gpa);
         function.frame_locs.deinit(gpa);
         function.loops.deinit(gpa);
+        function.loop_switches.deinit(gpa);
         var block_it = function.blocks.valueIterator();
         while (block_it.next()) |block| block.deinit(gpa);
         function.blocks.deinit(gpa);
@@ -912,6 +924,7 @@ pub fn generate(
         function.exitlude_jump_relocs.deinit(gpa);
         function.mir_instructions.deinit(gpa);
         function.mir_extra.deinit(gpa);
+        function.mir_table.deinit(gpa);
     }
     try function.inst_tracking.ensureTotalCapacity(gpa, Temp.Index.max);
     for (0..Temp.Index.max) |temp_index| {
@@ -978,6 +991,7 @@ pub fn generate(
     var mir: Mir = .{
         .instructions = function.mir_instructions.toOwnedSlice(),
         .extra = try function.mir_extra.toOwnedSlice(gpa),
+        .table = try function.mir_table.toOwnedSlice(gpa),
         .frame_locs = function.frame_locs.toOwnedSlice(),
     };
     defer mir.deinit(gpa);
@@ -1012,7 +1026,6 @@ pub fn generate(
         },
         .prev_di_pc = 0,
     };
-    defer emit.deinit();
     emit.emitMir() catch |err| switch (err) {
         error.LowerFail, error.EmitFail => return function.failMsg(emit.lower.err_msg.?),
 
@@ -1056,6 +1069,7 @@ pub fn generateLazy(
     defer {
         function.mir_instructions.deinit(gpa);
         function.mir_extra.deinit(gpa);
+        function.mir_table.deinit(gpa);
     }
 
     function.genLazy(lazy_sym) catch |err| switch (err) {
@@ -1067,6 +1081,7 @@ pub fn generateLazy(
     var mir: Mir = .{
         .instructions = function.mir_instructions.toOwnedSlice(),
         .extra = try function.mir_extra.toOwnedSlice(gpa),
+        .table = try function.mir_table.toOwnedSlice(gpa),
         .frame_locs = function.frame_locs.toOwnedSlice(),
     };
     defer mir.deinit(gpa);
@@ -1093,7 +1108,6 @@ pub fn generateLazy(
         .prev_di_loc = undefined, // no debug info yet
         .prev_di_pc = undefined, // no debug info yet
     };
-    defer emit.deinit();
     emit.emitMir() catch |err| switch (err) {
         error.LowerFail, error.EmitFail => return function.failMsg(emit.lower.err_msg.?),
         error.InvalidInstruction => return function.fail("failed to find a viable x86 instruction (Zig compiler bug)", .{}),
@@ -1161,6 +1175,7 @@ fn formatWipMir(
         .mir = .{
             .instructions = data.self.mir_instructions.slice(),
             .extra = data.self.mir_extra.items,
+            .table = data.self.mir_table.items,
             .frame_locs = (std.MultiArrayList(Mir.FrameLoc){}).slice(),
         },
         .cc = .auto,
@@ -20748,25 +20763,195 @@ fn lowerBlock(self: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index
     self.getValueIfFree(tracking.short, inst);
 }
 
-fn lowerSwitchBr(self: *CodeGen, inst: Air.Inst.Index, switch_br: Air.UnwrappedSwitch, condition: MCValue) !void {
+fn lowerSwitchBr(
+    self: *CodeGen,
+    inst: Air.Inst.Index,
+    switch_br: Air.UnwrappedSwitch,
+    condition: MCValue,
+    condition_dies: bool,
+    is_loop: bool,
+) !void {
     const zcu = self.pt.zcu;
     const condition_ty = self.typeOf(switch_br.operand);
-    const liveness = try self.liveness.getSwitchBr(self.gpa, inst, switch_br.cases_len + 1);
-    defer self.gpa.free(liveness.deaths);
 
-    const signedness = switch (condition_ty.zigTypeTag(zcu)) {
-        .bool, .pointer => .unsigned,
-        .int, .@"enum", .error_set => condition_ty.intInfo(zcu).signedness,
-        else => unreachable,
+    const ExpectedContents = extern struct {
+        liveness_deaths: [1 << 8 | 1]Air.Inst.Index,
+        bigint_limbs: [std.math.big.int.calcTwosCompLimbCount(1 << 8)]std.math.big.Limb,
+        relocs: [1 << 6]Mir.Inst.Index,
     };
+    var stack align(@max(@alignOf(ExpectedContents), @alignOf(std.heap.StackFallbackAllocator(0)))) =
+        std.heap.stackFallback(@sizeOf(ExpectedContents), self.gpa);
+    const allocator = stack.get();
 
     self.scope_generation += 1;
     const state = try self.saveState();
 
-    var it = switch_br.iterateCases();
-    while (it.next()) |case| {
-        var relocs = try self.gpa.alloc(Mir.Inst.Index, case.items.len + case.ranges.len);
-        defer self.gpa.free(relocs);
+    const liveness = try self.liveness.getSwitchBr(allocator, inst, switch_br.cases_len + 1);
+    defer allocator.free(liveness.deaths);
+
+    if (!self.mod.pic and self.target.ofmt == .elf) table: {
+        var prong_items: u32 = 0;
+        var min: ?Value = null;
+        var max: ?Value = null;
+        {
+            var cases_it = switch_br.iterateCases();
+            while (cases_it.next()) |case| {
+                prong_items += @intCast(case.items.len + case.ranges.len);
+                for (case.items) |item| {
+                    const val = Value.fromInterned(item.toInterned().?);
+                    if (min == null or val.compareHetero(.lt, min.?, zcu)) min = val;
+                    if (max == null or val.compareHetero(.gt, max.?, zcu)) max = val;
+                }
+                for (case.ranges) |range| {
+                    const low = Value.fromInterned(range[0].toInterned().?);
+                    if (min == null or low.compareHetero(.lt, min.?, zcu)) min = low;
+                    const high = Value.fromInterned(range[1].toInterned().?);
+                    if (max == null or high.compareHetero(.gt, max.?, zcu)) max = high;
+                }
+            }
+        }
+        // This condition also triggers for switches with no non-else prongs and switches on bool.
+        if (prong_items < 1 << 2 or prong_items > 1 << 8) break :table;
+
+        var min_space: Value.BigIntSpace = undefined;
+        const min_bigint = min.?.toBigInt(&min_space, zcu);
+        var max_space: Value.BigIntSpace = undefined;
+        const max_bigint = max.?.toBigInt(&max_space, zcu);
+        const limbs = try allocator.alloc(
+            std.math.big.Limb,
+            @max(min_bigint.limbs.len, max_bigint.limbs.len) + 1,
+        );
+        defer allocator.free(limbs);
+        const table_len = table_len: {
+            var table_len_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined };
+            table_len_bigint.sub(max_bigint, min_bigint);
+            assert(table_len_bigint.positive); // min <= max
+            break :table_len @as(u11, table_len_bigint.toConst().to(u10) catch break :table) + 1; // no more than a 1024 entry table
+        };
+        assert(prong_items <= table_len); // each prong item introduces at least one unique integer to the range
+        if (prong_items < table_len >> 2) break :table; // no more than 75% waste
+
+        const condition_index = if (condition_dies and condition.isModifiable()) condition else condition_index: {
+            const condition_index = try self.allocTempRegOrMem(condition_ty, true);
+            try self.genCopy(condition_ty, condition_index, condition, .{});
+            break :condition_index condition_index;
+        };
+        try self.spillEflagsIfOccupied();
+        if (min.?.orderAgainstZero(zcu).compare(.neq)) try self.genBinOpMir(
+            .{ ._, .sub },
+            condition_ty,
+            condition_index,
+            .{ .air_ref = Air.internedToRef(min.?.toIntern()) },
+        );
+        const else_reloc = if (switch_br.else_body_len > 0) else_reloc: {
+            try self.genBinOpMir(.{ ._, .cmp }, condition_ty, condition_index, .{ .immediate = table_len - 1 });
+            break :else_reloc try self.asmJccReloc(.a, undefined);
+        } else undefined;
+        const table_start: u31 = @intCast(self.mir_table.items.len);
+        {
+            const condition_index_reg = if (condition_index.isRegister())
+                condition_index.getReg().?
+            else
+                try self.copyToTmpRegister(.usize, condition_index);
+            const condition_index_lock = self.register_manager.lockReg(condition_index_reg);
+            defer if (condition_index_lock) |lock| self.register_manager.unlockReg(lock);
+            try self.truncateRegister(condition_ty, condition_index_reg);
+            const ptr_size = @divExact(self.target.ptrBitWidth(), 8);
+            try self.asmMemory(.{ ._, .jmp }, .{
+                .base = .table,
+                .mod = .{ .rm = .{
+                    .size = .ptr,
+                    .index = registerAlias(condition_index_reg, ptr_size),
+                    .scale = .fromFactor(@intCast(ptr_size)),
+                    .disp = table_start * ptr_size,
+                } },
+            });
+        }
+        const else_reloc_marker: u32 = 0;
+        assert(self.mir_instructions.len > else_reloc_marker);
+        try self.mir_table.appendNTimes(self.gpa, else_reloc_marker, table_len);
+        if (is_loop) try self.loop_switches.putNoClobber(self.gpa, inst, .{
+            .start = table_start,
+            .len = table_len,
+            .min = min.?,
+            .else_relocs = if (switch_br.else_body_len > 0) .{ .forward = .empty } else .@"unreachable",
+        });
+        defer if (is_loop) {
+            var loop_switch_data = self.loop_switches.fetchRemove(inst).?.value;
+            switch (loop_switch_data.else_relocs) {
+                .@"unreachable", .backward => {},
+                .forward => |*else_relocs| else_relocs.deinit(self.gpa),
+            }
+        };
+        var cases_it = switch_br.iterateCases();
+        while (cases_it.next()) |case| {
+            {
+                const table = self.mir_table.items[table_start..][0..table_len];
+                for (case.items) |item| {
+                    const val = Value.fromInterned(item.toInterned().?);
+                    var val_space: Value.BigIntSpace = undefined;
+                    const val_bigint = val.toBigInt(&val_space, zcu);
+                    var index_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined };
+                    index_bigint.sub(val_bigint, min_bigint);
+                    table[index_bigint.toConst().to(u10) catch unreachable] = @intCast(self.mir_instructions.len);
+                }
+                for (case.ranges) |range| {
+                    var low_space: Value.BigIntSpace = undefined;
+                    const low_bigint = Value.fromInterned(range[0].toInterned().?).toBigInt(&low_space, zcu);
+                    var high_space: Value.BigIntSpace = undefined;
+                    const high_bigint = Value.fromInterned(range[1].toInterned().?).toBigInt(&high_space, zcu);
+                    var index_bigint: std.math.big.int.Mutable = .{ .limbs = limbs, .positive = undefined, .len = undefined };
+                    index_bigint.sub(low_bigint, min_bigint);
+                    const start = index_bigint.toConst().to(u10) catch unreachable;
+                    index_bigint.sub(high_bigint, min_bigint);
+                    const end = @as(u11, index_bigint.toConst().to(u10) catch unreachable) + 1;
+                    @memset(table[start..end], @intCast(self.mir_instructions.len));
+                }
+            }
+
+            for (liveness.deaths[case.idx]) |operand| try self.processDeath(operand);
+
+            try self.genBodyBlock(case.body);
+            try self.restoreState(state, &.{}, .{
+                .emit_instructions = false,
+                .update_tracking = true,
+                .resurrect = true,
+                .close_scope = true,
+            });
+        }
+        if (switch_br.else_body_len > 0) {
+            const else_body = cases_it.elseBody();
+
+            const else_deaths = liveness.deaths.len - 1;
+            for (liveness.deaths[else_deaths]) |operand| try self.processDeath(operand);
+
+            self.performReloc(else_reloc);
+            if (is_loop) {
+                const loop_switch_data = self.loop_switches.getPtr(inst).?;
+                for (loop_switch_data.else_relocs.forward.items) |reloc| self.performReloc(reloc);
+                loop_switch_data.else_relocs.forward.deinit(self.gpa);
+                loop_switch_data.else_relocs = .{ .backward = @intCast(self.mir_instructions.len) };
+            }
+            for (self.mir_table.items[table_start..][0..table_len]) |*entry| if (entry.* == else_reloc_marker) {
+                entry.* = @intCast(self.mir_instructions.len);
+            };
+
+            try self.genBodyBlock(else_body);
+            try self.restoreState(state, &.{}, .{
+                .emit_instructions = false,
+                .update_tracking = true,
+                .resurrect = true,
+                .close_scope = true,
+            });
+        }
+        return;
+    }
+
+    const signedness = if (condition_ty.isAbiInt(zcu)) condition_ty.intInfo(zcu).signedness else .unsigned;
+    var cases_it = switch_br.iterateCases();
+    while (cases_it.next()) |case| {
+        var relocs = try allocator.alloc(Mir.Inst.Index, case.items.len + case.ranges.len);
+        defer allocator.free(relocs);
 
         try self.spillEflagsIfOccupied();
         for (case.items, relocs[0..case.items.len]) |item, *reloc| {
@@ -20849,9 +21034,8 @@ fn lowerSwitchBr(self: *CodeGen, inst: Air.Inst.Index, switch_br: Air.UnwrappedS
         // Relocate the "skip" branch to fall through to the next case.
         self.performReloc(skip_case_reloc);
     }
-
     if (switch_br.else_body_len > 0) {
-        const else_body = it.elseBody();
+        const else_body = cases_it.elseBody();
 
         const else_deaths = liveness.deaths.len - 1;
         for (liveness.deaths[else_deaths]) |operand| try self.processDeath(operand);
@@ -20873,11 +21057,11 @@ fn airSwitchBr(self: *CodeGen, inst: Air.Inst.Index) !void {
     // If the condition dies here in this switch instruction, process
     // that death now instead of later as this has an effect on
     // whether it needs to be spilled in the branches
-    if (self.liveness.operandDies(inst, 0)) {
+    const condition_dies = self.liveness.operandDies(inst, 0);
+    if (condition_dies) {
         if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
     }
-
-    try self.lowerSwitchBr(inst, switch_br, condition);
+    try self.lowerSwitchBr(inst, switch_br, condition, condition_dies, false);
 
     // We already took care of pl_op.operand earlier, so there's nothing left to do
 }
@@ -20915,7 +21099,7 @@ fn airLoopSwitchBr(self: *CodeGen, inst: Air.Inst.Index) !void {
     // Stop tracking block result without forgetting tracking info
     try self.freeValue(mat_cond);
 
-    try self.lowerSwitchBr(inst, switch_br, mat_cond);
+    try self.lowerSwitchBr(inst, switch_br, mat_cond, true, true);
 
     try self.processDeath(inst);
 }
@@ -20924,8 +21108,67 @@ fn airSwitchDispatch(self: *CodeGen, inst: Air.Inst.Index) !void {
     const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
 
     const block_ty = self.typeOfIndex(br.block_inst);
-    const block_tracking = self.inst_tracking.getPtr(br.block_inst).?;
     const loop_data = self.loops.getPtr(br.block_inst).?;
+    if (self.loop_switches.getPtr(br.block_inst)) |table| {
+        // Process operand death so that it is properly accounted for in the State below.
+        const condition_dies = self.liveness.operandDies(inst, 0);
+
+        try self.restoreState(loop_data.state, &.{}, .{
+            .emit_instructions = true,
+            .update_tracking = false,
+            .resurrect = false,
+            .close_scope = false,
+        });
+
+        const condition_ty = self.typeOf(br.operand);
+        const condition = try self.resolveInst(br.operand);
+        const condition_index = if (condition_dies and condition.isModifiable()) condition else condition_index: {
+            const condition_index = try self.allocTempRegOrMem(condition_ty, true);
+            try self.genCopy(condition_ty, condition_index, condition, .{});
+            break :condition_index condition_index;
+        };
+        try self.spillEflagsIfOccupied();
+        if (table.min.orderAgainstZero(self.pt.zcu).compare(.neq)) try self.genBinOpMir(
+            .{ ._, .sub },
+            condition_ty,
+            condition_index,
+            .{ .air_ref = Air.internedToRef(table.min.toIntern()) },
+        );
+        switch (table.else_relocs) {
+            .@"unreachable" => {},
+            .forward => |*else_relocs| {
+                try self.genBinOpMir(.{ ._, .cmp }, condition_ty, condition_index, .{ .immediate = table.len - 1 });
+                try else_relocs.append(self.gpa, try self.asmJccReloc(.a, undefined));
+            },
+            .backward => |else_reloc| {
+                try self.genBinOpMir(.{ ._, .cmp }, condition_ty, condition_index, .{ .immediate = table.len - 1 });
+                _ = try self.asmJccReloc(.a, else_reloc);
+            },
+        }
+        {
+            const condition_index_reg = if (condition_index.isRegister())
+                condition_index.getReg().?
+            else
+                try self.copyToTmpRegister(.usize, condition_index);
+            const condition_index_lock = self.register_manager.lockReg(condition_index_reg);
+            defer if (condition_index_lock) |lock| self.register_manager.unlockReg(lock);
+            try self.truncateRegister(condition_ty, condition_index_reg);
+            const ptr_size = @divExact(self.target.ptrBitWidth(), 8);
+            try self.asmMemory(.{ ._, .jmp }, .{
+                .base = .table,
+                .mod = .{ .rm = .{
+                    .size = .ptr,
+                    .index = registerAlias(condition_index_reg, ptr_size),
+                    .scale = .fromFactor(@intCast(ptr_size)),
+                    .disp = @intCast(table.start * ptr_size),
+                } },
+            });
+        }
+
+        return self.finishAir(inst, .none, .{ br.operand, .none, .none });
+    }
+
+    const block_tracking = self.inst_tracking.getPtr(br.block_inst).?;
     done: {
         try self.getValue(block_tracking.short, null);
         const src_mcv = try self.resolveInst(br.operand);
@@ -22543,6 +22786,7 @@ fn genSetMem(
         .none => .{ .immediate = @bitCast(@as(i64, disp)) },
         .reg => |base_reg| .{ .register_offset = .{ .reg = base_reg, .off = disp } },
         .frame => |base_frame_index| .{ .lea_frame = .{ .index = base_frame_index, .off = disp } },
+        .table => unreachable,
         .reloc => |sym_index| .{ .lea_symbol = .{ .sym_index = sym_index, .off = disp } },
     };
     switch (src_mcv) {
@@ -22652,6 +22896,7 @@ fn genSetMem(
                     .index = frame_index,
                     .off = disp,
                 }).compare(.gte, src_align),
+                .table => unreachable,
                 .reloc => false,
             })).write(
                 self,
@@ -23260,6 +23505,7 @@ fn airCmpxchg(self: *CodeGen, inst: Air.Inst.Index) !void {
     const ptr_lock = switch (ptr_mem.base) {
         .none, .frame, .reloc => null,
         .reg => |reg| self.register_manager.lockReg(reg),
+        .table => unreachable,
     };
     defer if (ptr_lock) |lock| self.register_manager.unlockReg(lock);
 
@@ -23327,6 +23573,7 @@ fn atomicOp(
     const mem_lock = switch (ptr_mem.base) {
         .none, .frame, .reloc => null,
         .reg => |reg| self.register_manager.lockReg(reg),
+        .table => unreachable,
     };
     defer if (mem_lock) |lock| self.register_manager.unlockReg(lock);
 
src/arch/x86_64/Emit.zig
@@ -10,22 +10,21 @@ prev_di_loc: Loc,
 /// Relative to the beginning of `code`.
 prev_di_pc: usize,
 
-code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .empty,
-relocs: std.ArrayListUnmanaged(Reloc) = .empty,
-
 pub const Error = Lower.Error || error{
     EmitFail,
 } || link.File.UpdateDebugInfoError;
 
 pub fn emitMir(emit: *Emit) Error!void {
     const gpa = emit.lower.bin_file.comp.gpa;
+    const code_offset_mapping = try emit.lower.allocator.alloc(u32, emit.lower.mir.instructions.len);
+    defer emit.lower.allocator.free(code_offset_mapping);
+    var relocs: std.ArrayListUnmanaged(Reloc) = .empty;
+    defer relocs.deinit(emit.lower.allocator);
+    var table_relocs: std.ArrayListUnmanaged(TableReloc) = .empty;
+    defer table_relocs.deinit(emit.lower.allocator);
     for (0..emit.lower.mir.instructions.len) |mir_i| {
         const mir_index: Mir.Inst.Index = @intCast(mir_i);
-        try emit.code_offset_mapping.putNoClobber(
-            emit.lower.allocator,
-            mir_index,
-            @intCast(emit.code.items.len),
-        );
+        code_offset_mapping[mir_index] = @intCast(emit.code.items.len);
         const lowered = try emit.lower.lowerMir(mir_index);
         var lowered_relocs = lowered.relocs;
         for (lowered.insts, 0..) |lowered_inst, lowered_index| {
@@ -89,13 +88,17 @@ pub fn emitMir(emit: *Emit) Error!void {
                 lowered_relocs[0].lowered_inst_index == lowered_index) : ({
                 lowered_relocs = lowered_relocs[1..];
             }) switch (lowered_relocs[0].target) {
-                .inst => |target| try emit.relocs.append(emit.lower.allocator, .{
+                .inst => |target| try relocs.append(emit.lower.allocator, .{
                     .source = start_offset,
                     .source_offset = end_offset - 4,
                     .target = target,
                     .target_offset = lowered_relocs[0].off,
                     .length = @intCast(end_offset - start_offset),
                 }),
+                .table => try table_relocs.append(emit.lower.allocator, .{
+                    .source_offset = end_offset - 4,
+                    .target_offset = lowered_relocs[0].off,
+                }),
                 .linker_extern_fn => |sym_index| if (emit.lower.bin_file.cast(.elf)) |elf_file| {
                     // Add relocation to the decl.
                     const zo = elf_file.zigObjectPtr().?;
@@ -103,7 +106,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                     const r_type = @intFromEnum(std.elf.R_X86_64.PLT32);
                     try atom_ptr.addReloc(gpa, .{
                         .r_offset = end_offset - 4,
-                        .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type,
+                        .r_info = @as(u64, sym_index) << 32 | r_type,
                         .r_addend = lowered_relocs[0].off - 4,
                     }, zo);
                 } else if (emit.lower.bin_file.cast(.macho)) |macho_file| {
@@ -150,7 +153,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                     const r_type = @intFromEnum(std.elf.R_X86_64.TLSLD);
                     try atom.addReloc(gpa, .{
                         .r_offset = end_offset - 4,
-                        .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type,
+                        .r_info = @as(u64, sym_index) << 32 | r_type,
                         .r_addend = lowered_relocs[0].off - 4,
                     }, zo);
                 },
@@ -161,7 +164,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                     const r_type = @intFromEnum(std.elf.R_X86_64.DTPOFF32);
                     try atom.addReloc(gpa, .{
                         .r_offset = end_offset - 4,
-                        .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type,
+                        .r_info = @as(u64, sym_index) << 32 | r_type,
                         .r_addend = lowered_relocs[0].off,
                     }, zo);
                 },
@@ -176,7 +179,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                             @intFromEnum(std.elf.R_X86_64.PC32);
                         try atom.addReloc(gpa, .{
                             .r_offset = end_offset - 4,
-                            .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type,
+                            .r_info = @as(u64, sym_index) << 32 | r_type,
                             .r_addend = lowered_relocs[0].off - 4,
                         }, zo);
                     } else {
@@ -186,7 +189,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                             @intFromEnum(std.elf.R_X86_64.@"32");
                         try atom.addReloc(gpa, .{
                             .r_offset = end_offset - 4,
-                            .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type,
+                            .r_info = @as(u64, sym_index) << 32 | r_type,
                             .r_addend = lowered_relocs[0].off,
                         }, zo);
                     }
@@ -412,7 +415,7 @@ pub fn emitMir(emit: *Emit) Error!void {
                                             loc_buf[0] = switch (mem.base()) {
                                                 .none => .{ .constu = 0 },
                                                 .reg => |reg| .{ .breg = reg.dwarfNum() },
-                                                .frame => unreachable,
+                                                .frame, .table => unreachable,
                                                 .reloc => |sym_index| .{ .addr = .{ .sym = sym_index } },
                                             };
                                             break :base &loc_buf[0];
@@ -463,13 +466,40 @@ pub fn emitMir(emit: *Emit) Error!void {
             }
         }
     }
-    try emit.fixupRelocs();
-}
+    {
+        // TODO this function currently assumes all relocs via JMP/CALL instructions are 32bit in size.
+        // This should be reversed like it is done in aarch64 MIR emit code: start with the smallest
+        // possible resolution, i.e., 8bit, and iteratively converge on the minimum required resolution
+        // until the entire decl is correctly emitted with all JMP/CALL instructions within range.
+        for (relocs.items) |reloc| {
+            const target = code_offset_mapping[reloc.target];
+            const disp = @as(i64, @intCast(target)) - @as(i64, @intCast(reloc.source + reloc.length)) + reloc.target_offset;
+            std.mem.writeInt(i32, emit.code.items[reloc.source_offset..][0..4], @intCast(disp), .little);
+        }
+    }
+    if (emit.lower.mir.table.len > 0) {
+        if (emit.lower.bin_file.cast(.elf)) |elf_file| {
+            const zo = elf_file.zigObjectPtr().?;
+            const atom = zo.symbol(emit.atom_index).atom(elf_file).?;
 
-pub fn deinit(emit: *Emit) void {
-    emit.relocs.deinit(emit.lower.allocator);
-    emit.code_offset_mapping.deinit(emit.lower.allocator);
-    emit.* = undefined;
+            const ptr_size = @divExact(emit.lower.target.ptrBitWidth(), 8);
+            var table_offset = std.mem.alignForward(u32, @intCast(emit.code.items.len), ptr_size);
+            for (table_relocs.items) |table_reloc| try atom.addReloc(gpa, .{
+                .r_offset = table_reloc.source_offset,
+                .r_info = @as(u64, emit.atom_index) << 32 | @intFromEnum(std.elf.R_X86_64.@"32"),
+                .r_addend = @as(i64, table_offset) + table_reloc.target_offset,
+            }, zo);
+            for (emit.lower.mir.table) |entry| {
+                try atom.addReloc(gpa, .{
+                    .r_offset = table_offset,
+                    .r_info = @as(u64, emit.atom_index) << 32 | @intFromEnum(std.elf.R_X86_64.@"64"),
+                    .r_addend = code_offset_mapping[entry],
+                }, zo);
+                table_offset += ptr_size;
+            }
+            try emit.code.appendNTimes(gpa, 0, table_offset - emit.code.items.len);
+        } else unreachable;
+    }
 }
 
 fn fail(emit: *Emit, comptime format: []const u8, args: anytype) Error {
@@ -481,7 +511,7 @@ fn fail(emit: *Emit, comptime format: []const u8, args: anytype) Error {
 
 const Reloc = struct {
     /// Offset of the instruction.
-    source: usize,
+    source: u32,
     /// Offset of the relocation within the instruction.
     source_offset: u32,
     /// Target of the relocation.
@@ -492,18 +522,12 @@ const Reloc = struct {
     length: u5,
 };
 
-fn fixupRelocs(emit: *Emit) Error!void {
-    // TODO this function currently assumes all relocs via JMP/CALL instructions are 32bit in size.
-    // This should be reversed like it is done in aarch64 MIR emit code: start with the smallest
-    // possible resolution, i.e., 8bit, and iteratively converge on the minimum required resolution
-    // until the entire decl is correctly emitted with all JMP/CALL instructions within range.
-    for (emit.relocs.items) |reloc| {
-        const target = emit.code_offset_mapping.get(reloc.target) orelse
-            return emit.fail("JMP/CALL relocation target not found!", .{});
-        const disp = @as(i64, @intCast(target)) - @as(i64, @intCast(reloc.source + reloc.length)) + reloc.target_offset;
-        std.mem.writeInt(i32, emit.code.items[reloc.source_offset..][0..4], @intCast(disp), .little);
-    }
-}
+const TableReloc = struct {
+    /// Offset of the relocation.
+    source_offset: u32,
+    /// Offset from the start of the table.
+    target_offset: i32,
+};
 
 const Loc = struct {
     line: u32,
src/arch/x86_64/encoder.zig
@@ -138,7 +138,7 @@ pub const Instruction = struct {
                 .moffs => true,
                 .rip => false,
                 .sib => |s| switch (s.base) {
-                    .none, .frame, .reloc => false,
+                    .none, .frame, .table, .reloc => false,
                     .reg => |reg| reg.class() == .segment,
                 },
             };
@@ -161,9 +161,9 @@ pub const Instruction = struct {
 
         pub fn disp(mem: Memory) Immediate {
             return switch (mem) {
-                .sib => |s| Immediate.s(s.disp),
-                .rip => |r| Immediate.s(r.disp),
-                .moffs => |m| Immediate.u(m.offset),
+                .sib => |s| .s(s.disp),
+                .rip => |r| .s(r.disp),
+                .moffs => |m| .u(m.offset),
             };
         }
 
@@ -277,6 +277,7 @@ pub const Instruction = struct {
                             .none => any = false,
                             .reg => |reg| try writer.print("{s}", .{@tagName(reg)}),
                             .frame => |frame_index| try writer.print("{}", .{frame_index}),
+                            .table => try writer.print("Table", .{}),
                             .reloc => |sym_index| try writer.print("Symbol({d})", .{sym_index}),
                         }
                         if (mem.scaleIndex()) |si| {
@@ -614,7 +615,7 @@ pub const Instruction = struct {
         switch (mem) {
             .moffs => unreachable,
             .sib => |sib| switch (sib.base) {
-                .none => {
+                .none, .table => {
                     try encoder.modRm_SIBDisp0(operand_enc);
                     if (mem.scaleIndex()) |si| {
                         const scale = math.log2_int(u4, si.scale);
@@ -1191,7 +1192,7 @@ const TestEncode = struct {
     ) !void {
         var stream = std.io.fixedBufferStream(&enc.buffer);
         var count_writer = std.io.countingWriter(stream.writer());
-        const inst = try Instruction.new(.none, mnemonic, ops);
+        const inst: Instruction = try .new(.none, mnemonic, ops);
         try inst.encode(count_writer.writer(), .{});
         enc.index = count_writer.bytes_written;
     }
@@ -1205,9 +1206,9 @@ test "encode" {
     var buf = std.ArrayList(u8).init(testing.allocator);
     defer buf.deinit();
 
-    const inst = try Instruction.new(.none, .mov, &.{
+    const inst: Instruction = try .new(.none, .mov, &.{
         .{ .reg = .rbx },
-        .{ .imm = Instruction.Immediate.u(4) },
+        .{ .imm = .u(4) },
     });
     try inst.encode(buf.writer(), .{});
     try testing.expectEqualSlices(u8, &.{ 0x48, 0xc7, 0xc3, 0x4, 0x0, 0x0, 0x0 }, buf.items);
@@ -1217,47 +1218,47 @@ test "lower I encoding" {
     var enc = TestEncode{};
 
     try enc.encode(.push, &.{
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x6A\x10", enc.code(), "push 0x10");
 
     try enc.encode(.push, &.{
-        .{ .imm = Instruction.Immediate.u(0x1000) },
+        .{ .imm = .u(0x1000) },
     });
     try expectEqualHexStrings("\x66\x68\x00\x10", enc.code(), "push 0x1000");
 
     try enc.encode(.push, &.{
-        .{ .imm = Instruction.Immediate.u(0x10000000) },
+        .{ .imm = .u(0x10000000) },
     });
     try expectEqualHexStrings("\x68\x00\x00\x00\x10", enc.code(), "push 0x10000000");
 
     try enc.encode(.adc, &.{
         .{ .reg = .rax },
-        .{ .imm = Instruction.Immediate.u(0x10000000) },
+        .{ .imm = .u(0x10000000) },
     });
     try expectEqualHexStrings("\x48\x15\x00\x00\x00\x10", enc.code(), "adc rax, 0x10000000");
 
     try enc.encode(.add, &.{
         .{ .reg = .al },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x04\x10", enc.code(), "add al, 0x10");
 
     try enc.encode(.add, &.{
         .{ .reg = .rax },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10");
 
     try enc.encode(.sbb, &.{
         .{ .reg = .ax },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x66\x1D\x10\x00", enc.code(), "sbb ax, 0x10");
 
     try enc.encode(.xor, &.{
         .{ .reg = .al },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x34\x10", enc.code(), "xor al, 0x10");
 }
@@ -1267,43 +1268,43 @@ test "lower MI encoding" {
 
     try enc.encode(.mov, &.{
         .{ .reg = .r12 },
-        .{ .imm = Instruction.Immediate.u(0x1000) },
+        .{ .imm = .u(0x1000) },
     });
     try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000");
 
     try enc.encode(.mov, &.{
         .{ .mem = Instruction.Memory.initSib(.byte, .{ .base = .{ .reg = .r12 } }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x41\xC6\x04\x24\x10", enc.code(), "mov BYTE PTR [r12], 0x10");
 
     try enc.encode(.mov, &.{
         .{ .reg = .r12 },
-        .{ .imm = Instruction.Immediate.u(0x1000) },
+        .{ .imm = .u(0x1000) },
     });
     try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000");
 
     try enc.encode(.mov, &.{
         .{ .reg = .r12 },
-        .{ .imm = Instruction.Immediate.u(0x1000) },
+        .{ .imm = .u(0x1000) },
     });
     try expectEqualHexStrings("\x49\xC7\xC4\x00\x10\x00\x00", enc.code(), "mov r12, 0x1000");
 
     try enc.encode(.mov, &.{
         .{ .reg = .rax },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", enc.code(), "mov rax, 0x10");
 
     try enc.encode(.mov, &.{
         .{ .mem = Instruction.Memory.initSib(.dword, .{ .base = .{ .reg = .r11 } }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", enc.code(), "mov DWORD PTR [r11], 0x10");
 
     try enc.encode(.mov, &.{
         .{ .mem = Instruction.Memory.initRip(.qword, 0x10) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings(
         "\x48\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00",
@@ -1313,19 +1314,19 @@ test "lower MI encoding" {
 
     try enc.encode(.mov, &.{
         .{ .mem = Instruction.Memory.initSib(.qword, .{ .base = .{ .reg = .rbp }, .disp = -8 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x48\xc7\x45\xf8\x10\x00\x00\x00", enc.code(), "mov QWORD PTR [rbp - 8], 0x10");
 
     try enc.encode(.mov, &.{
         .{ .mem = Instruction.Memory.initSib(.word, .{ .base = .{ .reg = .rbp }, .disp = -2 }) },
-        .{ .imm = Instruction.Immediate.s(-16) },
+        .{ .imm = .s(-16) },
     });
     try expectEqualHexStrings("\x66\xC7\x45\xFE\xF0\xFF", enc.code(), "mov WORD PTR [rbp - 2], -16");
 
     try enc.encode(.mov, &.{
         .{ .mem = Instruction.Memory.initSib(.byte, .{ .base = .{ .reg = .rbp }, .disp = -1 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\xC6\x45\xFF\x10", enc.code(), "mov BYTE PTR [rbp - 1], 0x10");
 
@@ -1335,7 +1336,7 @@ test "lower MI encoding" {
             .disp = 0x10000000,
             .scale_index = .{ .scale = 2, .index = .rcx },
         }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings(
         "\x48\xC7\x04\x4D\x00\x00\x00\x10\x10\x00\x00\x00",
@@ -1345,43 +1346,43 @@ test "lower MI encoding" {
 
     try enc.encode(.adc, &.{
         .{ .mem = Instruction.Memory.initSib(.byte, .{ .base = .{ .reg = .rbp }, .disp = -0x10 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x80\x55\xF0\x10", enc.code(), "adc BYTE PTR [rbp - 0x10], 0x10");
 
     try enc.encode(.adc, &.{
         .{ .mem = Instruction.Memory.initRip(.qword, 0) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x48\x83\x15\x00\x00\x00\x00\x10", enc.code(), "adc QWORD PTR [rip], 0x10");
 
     try enc.encode(.adc, &.{
         .{ .reg = .rax },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x48\x83\xD0\x10", enc.code(), "adc rax, 0x10");
 
     try enc.encode(.add, &.{
         .{ .mem = Instruction.Memory.initSib(.dword, .{ .base = .{ .reg = .rdx }, .disp = -8 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x83\x42\xF8\x10", enc.code(), "add DWORD PTR [rdx - 8], 0x10");
 
     try enc.encode(.add, &.{
         .{ .reg = .rax },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x48\x83\xC0\x10", enc.code(), "add rax, 0x10");
 
     try enc.encode(.add, &.{
         .{ .mem = Instruction.Memory.initSib(.qword, .{ .base = .{ .reg = .rbp }, .disp = -0x10 }) },
-        .{ .imm = Instruction.Immediate.s(-0x10) },
+        .{ .imm = .s(-0x10) },
     });
     try expectEqualHexStrings("\x48\x83\x45\xF0\xF0", enc.code(), "add QWORD PTR [rbp - 0x10], -0x10");
 
     try enc.encode(.@"and", &.{
         .{ .mem = Instruction.Memory.initSib(.dword, .{ .base = .{ .reg = .ds }, .disp = 0x10000000 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings(
         "\x83\x24\x25\x00\x00\x00\x10\x10",
@@ -1391,7 +1392,7 @@ test "lower MI encoding" {
 
     try enc.encode(.@"and", &.{
         .{ .mem = Instruction.Memory.initSib(.dword, .{ .base = .{ .reg = .es }, .disp = 0x10000000 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings(
         "\x26\x83\x24\x25\x00\x00\x00\x10\x10",
@@ -1401,7 +1402,7 @@ test "lower MI encoding" {
 
     try enc.encode(.@"and", &.{
         .{ .mem = Instruction.Memory.initSib(.dword, .{ .base = .{ .reg = .r12 }, .disp = 0x10000000 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings(
         "\x41\x83\xA4\x24\x00\x00\x00\x10\x10",
@@ -1411,7 +1412,7 @@ test "lower MI encoding" {
 
     try enc.encode(.sub, &.{
         .{ .mem = Instruction.Memory.initSib(.dword, .{ .base = .{ .reg = .r11 }, .disp = 0x10000000 }) },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings(
         "\x41\x83\xAB\x00\x00\x00\x10\x10",
@@ -1630,14 +1631,14 @@ test "lower RMI encoding" {
     try enc.encode(.imul, &.{
         .{ .reg = .r11 },
         .{ .reg = .r12 },
-        .{ .imm = Instruction.Immediate.s(-2) },
+        .{ .imm = .s(-2) },
     });
     try expectEqualHexStrings("\x4D\x6B\xDC\xFE", enc.code(), "imul r11, r12, -2");
 
     try enc.encode(.imul, &.{
         .{ .reg = .r11 },
         .{ .mem = Instruction.Memory.initRip(.qword, -16) },
-        .{ .imm = Instruction.Immediate.s(-1024) },
+        .{ .imm = .s(-1024) },
     });
     try expectEqualHexStrings(
         "\x4C\x69\x1D\xF0\xFF\xFF\xFF\x00\xFC\xFF\xFF",
@@ -1648,7 +1649,7 @@ test "lower RMI encoding" {
     try enc.encode(.imul, &.{
         .{ .reg = .bx },
         .{ .mem = Instruction.Memory.initSib(.word, .{ .base = .{ .reg = .rbp }, .disp = -16 }) },
-        .{ .imm = Instruction.Immediate.s(-1024) },
+        .{ .imm = .s(-1024) },
     });
     try expectEqualHexStrings(
         "\x66\x69\x5D\xF0\x00\xFC",
@@ -1659,7 +1660,7 @@ test "lower RMI encoding" {
     try enc.encode(.imul, &.{
         .{ .reg = .bx },
         .{ .mem = Instruction.Memory.initSib(.word, .{ .base = .{ .reg = .rbp }, .disp = -16 }) },
-        .{ .imm = Instruction.Immediate.u(1024) },
+        .{ .imm = .u(1024) },
     });
     try expectEqualHexStrings(
         "\x66\x69\x5D\xF0\x00\x04",
@@ -1775,7 +1776,7 @@ test "lower M encoding" {
     try expectEqualHexStrings("\x65\xFF\x14\x25\x00\x00\x00\x00", enc.code(), "call gs:0x0");
 
     try enc.encode(.call, &.{
-        .{ .imm = Instruction.Immediate.s(0) },
+        .{ .imm = .s(0) },
     });
     try expectEqualHexStrings("\xE8\x00\x00\x00\x00", enc.code(), "call 0x0");
 
@@ -1834,7 +1835,7 @@ test "lower OI encoding" {
 
     try enc.encode(.mov, &.{
         .{ .reg = .rax },
-        .{ .imm = Instruction.Immediate.u(0x1000000000000000) },
+        .{ .imm = .u(0x1000000000000000) },
     });
     try expectEqualHexStrings(
         "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10",
@@ -1844,7 +1845,7 @@ test "lower OI encoding" {
 
     try enc.encode(.mov, &.{
         .{ .reg = .r11 },
-        .{ .imm = Instruction.Immediate.u(0x1000000000000000) },
+        .{ .imm = .u(0x1000000000000000) },
     });
     try expectEqualHexStrings(
         "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10",
@@ -1854,19 +1855,19 @@ test "lower OI encoding" {
 
     try enc.encode(.mov, &.{
         .{ .reg = .r11d },
-        .{ .imm = Instruction.Immediate.u(0x10000000) },
+        .{ .imm = .u(0x10000000) },
     });
     try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", enc.code(), "mov r11d, 0x10000000");
 
     try enc.encode(.mov, &.{
         .{ .reg = .r11w },
-        .{ .imm = Instruction.Immediate.u(0x1000) },
+        .{ .imm = .u(0x1000) },
     });
     try expectEqualHexStrings("\x66\x41\xBB\x00\x10", enc.code(), "mov r11w, 0x1000");
 
     try enc.encode(.mov, &.{
         .{ .reg = .r11b },
-        .{ .imm = Instruction.Immediate.u(0x10) },
+        .{ .imm = .u(0x10) },
     });
     try expectEqualHexStrings("\x41\xB3\x10", enc.code(), "mov r11b, 0x10");
 }
@@ -1940,7 +1941,7 @@ test "lower NP encoding" {
 }
 
 fn invalidInstruction(mnemonic: Instruction.Mnemonic, ops: []const Instruction.Operand) !void {
-    const err = Instruction.new(.none, mnemonic, ops);
+    const err: Instruction = .new(.none, mnemonic, ops);
     try testing.expectError(error.InvalidInstruction, err);
 }
 
@@ -1988,12 +1989,12 @@ test "invalid instruction" {
         .{ .reg = .r12d },
     });
     try invalidInstruction(.push, &.{
-        .{ .imm = Instruction.Immediate.u(0x1000000000000000) },
+        .{ .imm = .u(0x1000000000000000) },
     });
 }
 
 fn cannotEncode(mnemonic: Instruction.Mnemonic, ops: []const Instruction.Operand) !void {
-    try testing.expectError(error.CannotEncode, Instruction.new(.none, mnemonic, ops));
+    try testing.expectError(error.CannotEncode, .new(.none, mnemonic, ops));
 }
 
 test "cannot encode" {
@@ -2177,7 +2178,7 @@ const Assembler = struct {
 
     pub fn assemble(as: *Assembler, writer: anytype) !void {
         while (try as.next()) |parsed_inst| {
-            const inst = try Instruction.new(.none, parsed_inst.mnemonic, &parsed_inst.ops);
+            const inst: Instruction = try .new(.none, parsed_inst.mnemonic, &parsed_inst.ops);
             try inst.encode(writer, .{});
         }
     }
src/arch/x86_64/Lower.zig
@@ -57,6 +57,7 @@ pub const Reloc = struct {
 
     const Target = union(enum) {
         inst: Mir.Inst.Index,
+        table,
         linker_reloc: u32,
         linker_tlsld: u32,
         linker_dtpoff: u32,
@@ -348,7 +349,7 @@ pub fn fail(lower: *Lower, comptime format: []const u8, args: anytype) Error {
     return error.LowerFail;
 }
 
-pub fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate {
+pub fn imm(lower: *const Lower, ops: Mir.Inst.Ops, i: u32) Immediate {
     return switch (ops) {
         .rri_s,
         .ri_s,
@@ -379,8 +380,16 @@ pub fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate {
     };
 }
 
-pub fn mem(lower: Lower, payload: u32) Memory {
-    return lower.mir.resolveFrameLoc(lower.mir.extraData(Mir.Memory, payload).data).decode();
+pub fn mem(lower: *Lower, payload: u32) Memory {
+    var m = lower.mir.resolveFrameLoc(lower.mir.extraData(Mir.Memory, payload).data).decode();
+    switch (m) {
+        .sib => |*sib| switch (sib.base) {
+            else => {},
+            .table => sib.disp = lower.reloc(.table, sib.disp).signed,
+        },
+        else => {},
+    }
+    return m;
 }
 
 fn reloc(lower: *Lower, target: Reloc.Target, off: i32) Immediate {
src/arch/x86_64/Mir.zig
@@ -9,6 +9,7 @@
 instructions: std.MultiArrayList(Inst).Slice,
 /// The meaning of this data is determined by `Inst.Tag` value.
 extra: []const u32,
+table: []const Inst.Index,
 frame_locs: std.MultiArrayList(FrameLoc).Slice,
 
 pub const Inst = struct {
@@ -1237,7 +1238,7 @@ pub const Memory = struct {
         size: bits.Memory.Size,
         index: Register,
         scale: bits.Memory.Scale,
-        _: u16 = undefined,
+        _: u15 = undefined,
     };
 
     pub fn encode(mem: bits.Memory) Memory {
@@ -1260,7 +1261,7 @@ pub const Memory = struct {
                 },
             },
             .base = switch (mem.base) {
-                .none => undefined,
+                .none, .table => undefined,
                 .reg => |reg| @intFromEnum(reg),
                 .frame => |frame_index| @intFromEnum(frame_index),
                 .reloc => |sym_index| sym_index,
@@ -1289,6 +1290,7 @@ pub const Memory = struct {
                         .none => .none,
                         .reg => .{ .reg = @enumFromInt(mem.base) },
                         .frame => .{ .frame = @enumFromInt(mem.base) },
+                        .table => .table,
                         .reloc => .{ .reloc = mem.base },
                     },
                     .scale_index = switch (mem.info.index) {
@@ -1317,6 +1319,7 @@ pub const Memory = struct {
 pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
     mir.instructions.deinit(gpa);
     gpa.free(mir.extra);
+    gpa.free(mir.table);
     mir.frame_locs.deinit(gpa);
     mir.* = undefined;
 }
@@ -1352,7 +1355,7 @@ pub fn resolveFrameAddr(mir: Mir, frame_addr: bits.FrameAddr) bits.RegisterOffse
 
 pub fn resolveFrameLoc(mir: Mir, mem: Memory) Memory {
     return switch (mem.info.base) {
-        .none, .reg, .reloc => mem,
+        .none, .reg, .table, .reloc => mem,
         .frame => if (mir.frame_locs.len > 0) .{
             .info = .{
                 .base = .reg,
src/link/MachO.zig
@@ -3548,7 +3548,7 @@ pub fn getTarget(self: MachO) std.Target {
 pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void {
     const tracy = trace(@src());
     defer tracy.end();
-    if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
+    if (builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
         try dir.copyFile(sub_path, dir, sub_path, .{});
     }
 }
src/Liveness.zig
@@ -719,32 +719,25 @@ pub const SwitchBrTable = struct {
 
 /// Caller owns the memory.
 pub fn getSwitchBr(l: Liveness, gpa: Allocator, inst: Air.Inst.Index, cases_len: u32) Allocator.Error!SwitchBrTable {
-    var index: usize = l.special.get(inst) orelse return SwitchBrTable{
-        .deaths = &.{},
-    };
+    var index: usize = l.special.get(inst) orelse return .{ .deaths = &.{} };
     const else_death_count = l.extra[index];
     index += 1;
 
-    var deaths = std.ArrayList([]const Air.Inst.Index).init(gpa);
-    defer deaths.deinit();
-    try deaths.ensureTotalCapacity(cases_len + 1);
+    var deaths = try gpa.alloc([]const Air.Inst.Index, cases_len);
+    errdefer gpa.free(deaths);
 
     var case_i: u32 = 0;
     while (case_i < cases_len - 1) : (case_i += 1) {
         const case_death_count: u32 = l.extra[index];
         index += 1;
-        const case_deaths: []const Air.Inst.Index = @ptrCast(l.extra[index..][0..case_death_count]);
+        deaths[case_i] = @ptrCast(l.extra[index..][0..case_death_count]);
         index += case_death_count;
-        deaths.appendAssumeCapacity(case_deaths);
     }
     {
         // Else
-        const else_deaths: []const Air.Inst.Index = @ptrCast(l.extra[index..][0..else_death_count]);
-        deaths.appendAssumeCapacity(else_deaths);
+        deaths[case_i] = @ptrCast(l.extra[index..][0..else_death_count]);
     }
-    return SwitchBrTable{
-        .deaths = try deaths.toOwnedSlice(),
-    };
+    return .{ .deaths = deaths };
 }
 
 /// Note that this information is technically redundant, but is useful for
test/behavior/align.zig
@@ -277,8 +277,8 @@ test "function alignment" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
 
-    // function alignment is a compile error on wasm32/wasm64
-    if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;
+    // function alignment is a compile error on wasm
+    if (native_arch.isWasm()) return error.SkipZigTest;
 
     const S = struct {
         fn alignExpr() align(@sizeOf(usize) * 2) i32 {
@@ -307,8 +307,8 @@ test "implicitly decreasing fn alignment" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
 
-    // function alignment is a compile error on wasm32/wasm64
-    if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;
+    // function alignment is a compile error on wasm
+    if (native_arch.isWasm()) return error.SkipZigTest;
 
     try testImplicitlyDecreaseFnAlign(alignedSmall, 1234);
     try testImplicitlyDecreaseFnAlign(alignedBig, 5678);
@@ -331,9 +331,9 @@ test "@alignCast functions" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
 
-    // function alignment is a compile error on wasm32/wasm64
-    if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;
-    if (native_arch == .thumb or native_arch == .thumbeb) return error.SkipZigTest;
+    // function alignment is a compile error on wasm
+    if (native_arch.isWasm()) return error.SkipZigTest;
+    if (native_arch.isThumb()) return error.SkipZigTest;
 
     try expect(fnExpectsOnly1(simple4) == 0x19);
 }
@@ -496,9 +496,9 @@ test "align(N) on functions" {
         return error.SkipZigTest;
     }
 
-    // function alignment is a compile error on wasm32/wasm64
-    if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;
-    if (native_arch == .thumb or native_arch == .thumbeb) return error.SkipZigTest;
+    // function alignment is a compile error on wasm
+    if (native_arch.isWasm()) return error.SkipZigTest;
+    if (native_arch.isThumb()) return error.SkipZigTest;
 
     try expect((@intFromPtr(&overaligned_fn) & (0x1000 - 1)) == 0);
 }
test/behavior/asm.zig
@@ -178,7 +178,7 @@ test "rw constraint (x86_64)" {
 }
 
 test "asm modifiers (AArch64)" {
-    if (builtin.target.cpu.arch != .aarch64) return error.SkipZigTest;
+    if (!builtin.target.cpu.arch.isAARCH64()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
     if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
test/behavior/call.zig
@@ -660,6 +660,7 @@ test "arguments pointed to on stack into tailcall" {
 
     switch (builtin.cpu.arch) {
         .wasm32,
+        .wasm64,
         .mips,
         .mipsel,
         .mips64,
test/behavior/cast.zig
@@ -124,7 +124,7 @@ test "@floatFromInt(f80)" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
@@ -1362,7 +1362,7 @@ test "cast f16 to wider types" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
test/behavior/eval.zig
@@ -522,7 +522,7 @@ test "runtime 128 bit integer division" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
test/behavior/math.zig
@@ -785,7 +785,7 @@ test "128-bit multiplication" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     {
@@ -1374,7 +1374,7 @@ test "remainder division" {
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     if (builtin.zig_backend == .stage2_llvm and builtin.os.tag == .windows) {
@@ -1527,7 +1527,7 @@ test "@round f80" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
@@ -1540,7 +1540,7 @@ test "@round f128" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
test/behavior/maximum_minimum.zig
@@ -122,7 +122,7 @@ test "@min/max for floats" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
test/behavior/muladd.zig
@@ -58,7 +58,7 @@ test "@mulAdd f80" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
@@ -79,7 +79,7 @@ test "@mulAdd f128" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
@@ -189,7 +189,7 @@ test "vector f80" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     try comptime vector80();
@@ -216,7 +216,7 @@ test "vector f128" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     try comptime vector128();
test/behavior/saturating_arithmetic.zig
@@ -164,10 +164,10 @@ test "saturating multiplication <= 32 bits" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
-    if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .wasm32) {
+    if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isWasm()) {
         // https://github.com/ziglang/zig/issues/9660
         return error.SkipZigTest;
     }
@@ -264,10 +264,10 @@ test "saturating multiplication" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
-    if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .wasm32) {
+    if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isWasm()) {
         // https://github.com/ziglang/zig/issues/9660
         return error.SkipZigTest;
     }
@@ -311,7 +311,7 @@ test "saturating shift-left" {
             try testSatShl(i8, 127, 1, 127);
             try testSatShl(i8, -128, 1, -128);
             // TODO: remove this check once #9668 is completed
-            if (builtin.cpu.arch != .wasm32) {
+            if (!builtin.cpu.arch.isWasm()) {
                 // skip testing ints > 64 bits on wasm due to miscompilation / wasmtime ci error
                 try testSatShl(i128, maxInt(i128), 64, maxInt(i128));
                 try testSatShl(u128, maxInt(u128), 64, maxInt(u128));
test/behavior/struct.zig
@@ -418,8 +418,8 @@ test "packed struct 24bits" {
     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (builtin.cpu.arch == .wasm32) return error.SkipZigTest; // TODO
-    if (comptime builtin.cpu.arch.isArm()) return error.SkipZigTest; // TODO
+    if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO
+    if (builtin.cpu.arch.isArm()) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
 
@@ -818,7 +818,7 @@ test "non-packed struct with u128 entry in union" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     const U = union(enum) {
@@ -941,7 +941,7 @@ test "tuple assigned to variable" {
 
 test "comptime struct field" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
-    if (comptime builtin.cpu.arch.isArm()) return error.SkipZigTest; // TODO
+    if (builtin.cpu.arch.isArm()) return error.SkipZigTest; // TODO
 
     const T = struct {
         a: i32,
test/behavior/var_args.zig
@@ -100,7 +100,7 @@ test "simple variadic function" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-    if (builtin.os.tag != .macos and comptime builtin.cpu.arch.isAARCH64()) {
+    if (builtin.os.tag != .macos and builtin.cpu.arch.isAARCH64()) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
@@ -161,7 +161,7 @@ test "coerce reference to var arg" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-    if (builtin.os.tag != .macos and comptime builtin.cpu.arch.isAARCH64()) {
+    if (builtin.os.tag != .macos and builtin.cpu.arch.isAARCH64()) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
@@ -194,7 +194,7 @@ test "variadic functions" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-    if (builtin.os.tag != .macos and comptime builtin.cpu.arch.isAARCH64()) {
+    if (builtin.os.tag != .macos and builtin.cpu.arch.isAARCH64()) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
@@ -239,7 +239,7 @@ test "copy VaList" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-    if (builtin.os.tag != .macos and comptime builtin.cpu.arch.isAARCH64()) {
+    if (builtin.os.tag != .macos and builtin.cpu.arch.isAARCH64()) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
@@ -273,7 +273,7 @@ test "unused VaList arg" {
     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
-    if (builtin.os.tag != .macos and comptime builtin.cpu.arch.isAARCH64()) {
+    if (builtin.os.tag != .macos and builtin.cpu.arch.isAARCH64()) {
         // https://github.com/ziglang/zig/issues/14096
         return error.SkipZigTest;
     }
test/behavior/vector.zig
@@ -101,7 +101,7 @@ test "vector float operators" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
 
     if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) {
@@ -754,7 +754,7 @@ test "vector reduce operation" {
     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
     if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArm()) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c and builtin.cpu.arch.isArm()) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
     if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21091
 
@@ -989,7 +989,7 @@ test "saturating multiplication" {
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     // TODO: once #9660 has been solved, remove this line
-    if (builtin.target.cpu.arch == .wasm32) return error.SkipZigTest;
+    if (builtin.target.cpu.arch.isWasm()) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest() !void {
@@ -1256,7 +1256,7 @@ test "byte vector initialized in inline function" {
     if (builtin.cpu.arch == .aarch64_be and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest;
 
     if (comptime builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .x86_64 and
-        builtin.cpu.features.isEnabled(@intFromEnum(std.Target.x86.Feature.avx512f)))
+        std.Target.x86.featureSetHas(builtin.cpu.features, .avx512f))
     {
         // TODO https://github.com/ziglang/zig/issues/13279
         return error.SkipZigTest;
test/behavior/wrapping_arithmetic.zig
@@ -83,7 +83,7 @@ test "wrapping multiplication" {
     if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
 
     // TODO: once #9660 has been solved, remove this line
-    if (builtin.cpu.arch == .wasm32) return error.SkipZigTest;
+    if (builtin.cpu.arch.isWasm()) return error.SkipZigTest;
 
     const S = struct {
         fn doTheTest() !void {