Commit 1f15e265fe

Alex Rønne Petersen <alex@alexrp.com>
2025-10-16 18:51:09
std.debug.cpu_context: sort context decls according to switch prongs (NFC)
1 parent 4c81a49
Changed files (1)
lib
std
lib/std/debug/cpu_context.zig
@@ -212,129 +212,63 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native {
     };
 }
 
-const X86 = struct {
-    /// The first 8 registers here intentionally match the order of registers in the x86 instruction
-    /// encoding. This order is inherited by the PUSHA instruction and the DWARF register mappings,
-    /// among other things.
-    pub const Gpr = enum {
-        // zig fmt: off
-        eax, ecx, edx, ebx,
-        esp, ebp, esi, edi,
-        eip,
-        // zig fmt: on
-    };
-    gprs: std.enums.EnumArray(Gpr, u32),
-
-    pub inline fn current() X86 {
-        var ctx: X86 = undefined;
-        asm volatile (
-            \\movl %%eax, 0x00(%%edi)
-            \\movl %%ecx, 0x04(%%edi)
-            \\movl %%edx, 0x08(%%edi)
-            \\movl %%ebx, 0x0c(%%edi)
-            \\movl %%esp, 0x10(%%edi)
-            \\movl %%ebp, 0x14(%%edi)
-            \\movl %%esi, 0x18(%%edi)
-            \\movl %%edi, 0x1c(%%edi)
-            \\call 1f
-            \\1:
-            \\popl 0x20(%%edi)
-            :
-            : [gprs] "{edi}" (&ctx.gprs.values),
-            : .{ .memory = true });
-        return ctx;
-    }
-
-    pub fn dwarfRegisterBytes(ctx: *X86, register_num: u16) DwarfRegisterError![]u8 {
-        // System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1
-        //   § 2.4.2 "DWARF Register Number Mapping"
-        switch (register_num) {
-            // The order of `Gpr` intentionally matches DWARF's mappings.
-            //
-            // x86-macos sometimes uses different mappings (ebp and esp are reversed when the unwind
-            // information is from `__eh_frame`). This deviation is not considered here, because
-            // x86-macos is a deprecated target which is not supported by the Zig Standard Library.
-            0...8 => return @ptrCast(&ctx.gprs.values[register_num]),
-
-            9 => return error.UnsupportedRegister, // eflags
-            11...18 => return error.UnsupportedRegister, // st0 - st7
-            21...28 => return error.UnsupportedRegister, // xmm0 - xmm7
-            29...36 => return error.UnsupportedRegister, // mm0 - mm7
-            39 => return error.UnsupportedRegister, // mxcsr
-            40...45 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs
-            48 => return error.UnsupportedRegister, // tr
-            49 => return error.UnsupportedRegister, // ldtr
-            93...100 => return error.UnsupportedRegister, // k0 - k7 (AVX-512)
-
-            else => return error.InvalidRegister,
-        }
-    }
-};
-
-const X86_64 = struct {
-    /// The order here intentionally matches the order of the DWARF register mappings. It's unclear
-    /// where those mappings actually originated from---the ordering of the first 4 registers seems
-    /// quite unusual---but it is currently convenient for us to match DWARF.
-    pub const Gpr = enum {
-        // zig fmt: off
-        rax, rdx, rcx, rbx,
-        rsi, rdi, rbp, rsp,
-        r8,  r9,  r10, r11,
-        r12, r13, r14, r15,
-        rip,
-        // zig fmt: on
-    };
-    gprs: std.enums.EnumArray(Gpr, u64),
+/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
+const Aarch64 = extern struct {
+    /// The numbered general-purpose registers X0 - X30.
+    x: [31]u64,
+    sp: u64,
+    pc: u64,
 
-    pub inline fn current() X86_64 {
-        var ctx: X86_64 = undefined;
+    pub inline fn current() Aarch64 {
+        var ctx: Aarch64 = undefined;
         asm volatile (
-            \\movq %%rax, 0x00(%%rdi)
-            \\movq %%rdx, 0x08(%%rdi)
-            \\movq %%rcx, 0x10(%%rdi)
-            \\movq %%rbx, 0x18(%%rdi)
-            \\movq %%rsi, 0x20(%%rdi)
-            \\movq %%rdi, 0x28(%%rdi)
-            \\movq %%rbp, 0x30(%%rdi)
-            \\movq %%rsp, 0x38(%%rdi)
-            \\movq %%r8,  0x40(%%rdi)
-            \\movq %%r9,  0x48(%%rdi)
-            \\movq %%r10, 0x50(%%rdi)
-            \\movq %%r11, 0x58(%%rdi)
-            \\movq %%r12, 0x60(%%rdi)
-            \\movq %%r13, 0x68(%%rdi)
-            \\movq %%r14, 0x70(%%rdi)
-            \\movq %%r15, 0x78(%%rdi)
-            \\leaq (%%rip), %%rax
-            \\movq %%rax, 0x80(%%rdi)
-            \\movq 0x00(%%rdi), %%rax
+            \\ stp x0,  x1,  [x0, #0x000]
+            \\ stp x2,  x3,  [x0, #0x010]
+            \\ stp x4,  x5,  [x0, #0x020]
+            \\ stp x6,  x7,  [x0, #0x030]
+            \\ stp x8,  x9,  [x0, #0x040]
+            \\ stp x10, x11, [x0, #0x050]
+            \\ stp x12, x13, [x0, #0x060]
+            \\ stp x14, x15, [x0, #0x070]
+            \\ stp x16, x17, [x0, #0x080]
+            \\ stp x18, x19, [x0, #0x090]
+            \\ stp x20, x21, [x0, #0x0a0]
+            \\ stp x22, x23, [x0, #0x0b0]
+            \\ stp x24, x25, [x0, #0x0c0]
+            \\ stp x26, x27, [x0, #0x0d0]
+            \\ stp x28, x29, [x0, #0x0e0]
+            \\ str x30, [x0, #0x0f0]
+            \\ mov x1, sp
+            \\ str x1, [x0, #0x0f8]
+            \\ adr x1, .
+            \\ str x1, [x0, #0x100]
+            \\ ldr x1, [x0, #0x008]
             :
-            : [gprs] "{rdi}" (&ctx.gprs.values),
+            : [ctx] "{x0}" (&ctx),
             : .{ .memory = true });
         return ctx;
     }
 
-    pub fn dwarfRegisterBytes(ctx: *X86_64, register_num: u16) DwarfRegisterError![]u8 {
-        // System V Application Binary Interface AMD64 Architecture Processor Supplement
-        //   § 3.6.2 "DWARF Register Number Mapping"
+    pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 {
+        // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names"
         switch (register_num) {
-            // The order of `Gpr` intentionally matches DWARF's mappings.
-            0...16 => return @ptrCast(&ctx.gprs.values[register_num]),
+            0...30 => return @ptrCast(&ctx.x[register_num]),
+            31 => return @ptrCast(&ctx.sp),
+            32 => return @ptrCast(&ctx.pc),
 
-            17...32 => return error.UnsupportedRegister, // xmm0 - xmm15
-            33...40 => return error.UnsupportedRegister, // st0 - st7
-            41...48 => return error.UnsupportedRegister, // mm0 - mm7
-            49 => return error.UnsupportedRegister, // rflags
-            50...55 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs
-            58...59 => return error.UnsupportedRegister, // fs.base, gs.base
-            62 => return error.UnsupportedRegister, // tr
-            63 => return error.UnsupportedRegister, // ldtr
-            64 => return error.UnsupportedRegister, // mxcsr
-            65 => return error.UnsupportedRegister, // fcw
-            66 => return error.UnsupportedRegister, // fsw
-            67...82 => return error.UnsupportedRegister, // xmm16 - xmm31 (AVX-512)
-            118...125 => return error.UnsupportedRegister, // k0 - k7 (AVX-512)
-            130...145 => return error.UnsupportedRegister, // r16 - r31 (APX)
+            33 => return error.UnsupportedRegister, // ELR_mode
+            34 => return error.UnsupportedRegister, // RA_SIGN_STATE
+            35 => return error.UnsupportedRegister, // TPIDRRO_ELO
+            36 => return error.UnsupportedRegister, // TPIDR_ELO
+            37 => return error.UnsupportedRegister, // TPIDR_EL1
+            38 => return error.UnsupportedRegister, // TPIDR_EL2
+            39 => return error.UnsupportedRegister, // TPIDR_EL3
+            40...45 => return error.UnsupportedRegister, // Reserved
+            46 => return error.UnsupportedRegister, // VG
+            47 => return error.UnsupportedRegister, // FFR
+            48...63 => return error.UnsupportedRegister, // P0 - P15
+            64...95 => return error.UnsupportedRegister, // V0 - V31
+            96...127 => return error.UnsupportedRegister, // Z0 - Z31
 
             else => return error.InvalidRegister,
         }
@@ -410,11 +344,11 @@ const Arm = struct {
     pub inline fn current() Arm {
         var ctx: Arm = undefined;
         asm volatile (
-            \\// For compatibility with Thumb, we can't write r13 (sp) or r15 (pc) with stm.
-            \\stm r0, {r0-r12}
-            \\str r13, [r0, #0x34]
-            \\str r14, [r0, #0x38]
-            \\str r15, [r0, #0x3c]
+            \\ // For compatibility with Thumb, we can't write r13 (sp) or r15 (pc) with stm.
+            \\ stm r0, {r0-r12}
+            \\ str r13, [r0, #0x34]
+            \\ str r14, [r0, #0x38]
+            \\ str r15, [r0, #0x3c]
             :
             : [r] "{r0}" (&ctx.r),
             : .{ .memory = true });
@@ -461,69 +395,6 @@ const Arm = struct {
     }
 };
 
-/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
-const Aarch64 = extern struct {
-    /// The numbered general-purpose registers X0 - X30.
-    x: [31]u64,
-    sp: u64,
-    pc: u64,
-
-    pub inline fn current() Aarch64 {
-        var ctx: Aarch64 = undefined;
-        asm volatile (
-            \\stp x0,  x1,  [x0, #0x000]
-            \\stp x2,  x3,  [x0, #0x010]
-            \\stp x4,  x5,  [x0, #0x020]
-            \\stp x6,  x7,  [x0, #0x030]
-            \\stp x8,  x9,  [x0, #0x040]
-            \\stp x10, x11, [x0, #0x050]
-            \\stp x12, x13, [x0, #0x060]
-            \\stp x14, x15, [x0, #0x070]
-            \\stp x16, x17, [x0, #0x080]
-            \\stp x18, x19, [x0, #0x090]
-            \\stp x20, x21, [x0, #0x0a0]
-            \\stp x22, x23, [x0, #0x0b0]
-            \\stp x24, x25, [x0, #0x0c0]
-            \\stp x26, x27, [x0, #0x0d0]
-            \\stp x28, x29, [x0, #0x0e0]
-            \\str x30, [x0, #0x0f0]
-            \\mov x1, sp
-            \\str x1, [x0, #0x0f8]
-            \\adr x1, .
-            \\str x1, [x0, #0x100]
-            \\ldr x1, [x0, #0x008]
-            :
-            : [gprs] "{x0}" (&ctx),
-            : .{ .memory = true });
-        return ctx;
-    }
-
-    pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 {
-        // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names"
-        switch (register_num) {
-            0...30 => return @ptrCast(&ctx.x[register_num]),
-            31 => return @ptrCast(&ctx.sp),
-            32 => return @ptrCast(&ctx.pc),
-
-            33 => return error.UnsupportedRegister, // ELR_mode
-            34 => return error.UnsupportedRegister, // RA_SIGN_STATE
-            35 => return error.UnsupportedRegister, // TPIDRRO_ELO
-            36 => return error.UnsupportedRegister, // TPIDR_ELO
-            37 => return error.UnsupportedRegister, // TPIDR_EL1
-            38 => return error.UnsupportedRegister, // TPIDR_EL2
-            39 => return error.UnsupportedRegister, // TPIDR_EL3
-            40...45 => return error.UnsupportedRegister, // Reserved
-            46 => return error.UnsupportedRegister, // VG
-            47 => return error.UnsupportedRegister, // FFR
-            48...63 => return error.UnsupportedRegister, // P0 - P15
-            64...95 => return error.UnsupportedRegister, // V0 - V31
-            96...127 => return error.UnsupportedRegister, // Z0 - Z31
-
-            else => return error.InvalidRegister,
-        }
-    }
-};
-
 /// This is an `extern struct` so that inline assembly in `current` can use field offsets.
 const Csky = extern struct {
     /// The numbered general-purpose registers r0 - r31.
@@ -600,7 +471,7 @@ const Hexagon = extern struct {
             \\ memw(r0 + #128) = r1
             \\ r1 = memw(r0 + #4)
             :
-            : [gprs] "{r0}" (&ctx),
+            : [ctx] "{r0}" (&ctx),
             : .{ .memory = true });
         return ctx;
     }
@@ -762,7 +633,7 @@ const LoongArch = extern struct {
                 \\ st.w $ra, $t0, 128
                 \\ ld.w $ra, $t0, 4
             :
-            : [gprs] "{$r12}" (&ctx),
+            : [ctx] "{$r12}" (&ctx),
             : .{ .memory = true });
         return ctx;
     }
@@ -908,7 +779,7 @@ const Mips = extern struct {
                 \\ lw $ra, 124($t4)
                 \\ .set pop
             :
-            : [gprs] "{$12}" (&ctx),
+            : [ctx] "{$12}" (&ctx),
             : .{ .memory = true });
         return ctx;
     }
@@ -1087,7 +958,7 @@ const Powerpc = extern struct {
                 \\ stw 8, 128(10)
                 \\ lwz 8, 32(10)
             :
-            : [gprs] "{r10}" (&ctx),
+            : [ctx] "{r10}" (&ctx),
             : .{ .lr = true, .memory = true });
         return ctx;
     }
@@ -1151,104 +1022,6 @@ const Powerpc = extern struct {
     }
 };
 
-/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
-const Sparc = extern struct {
-    g: [8]Gpr,
-    o: [8]Gpr,
-    l: [8]Gpr,
-    i: [8]Gpr,
-    pc: Gpr,
-
-    pub const Gpr = if (native_arch == .sparc64) u64 else u32;
-
-    pub inline fn current() Sparc {
-        flushWindows();
-
-        var ctx: Sparc = undefined;
-        asm volatile (if (Gpr == u64)
-                \\ stx %g0, [%l0 + 0]
-                \\ stx %g1, [%l0 + 8]
-                \\ stx %g2, [%l0 + 16]
-                \\ stx %g3, [%l0 + 24]
-                \\ stx %g4, [%l0 + 32]
-                \\ stx %g5, [%l0 + 40]
-                \\ stx %g6, [%l0 + 48]
-                \\ stx %g7, [%l0 + 56]
-                \\ stx %o0, [%l0 + 64]
-                \\ stx %o1, [%l0 + 72]
-                \\ stx %o2, [%l0 + 80]
-                \\ stx %o3, [%l0 + 88]
-                \\ stx %o4, [%l0 + 96]
-                \\ stx %o5, [%l0 + 104]
-                \\ stx %o6, [%l0 + 112]
-                \\ stx %o7, [%l0 + 120]
-                \\ stx %l0, [%l0 + 128]
-                \\ stx %l1, [%l0 + 136]
-                \\ stx %l2, [%l0 + 144]
-                \\ stx %l3, [%l0 + 152]
-                \\ stx %l4, [%l0 + 160]
-                \\ stx %l5, [%l0 + 168]
-                \\ stx %l6, [%l0 + 176]
-                \\ stx %l7, [%l0 + 184]
-                \\ stx %i0, [%l0 + 192]
-                \\ stx %i1, [%l0 + 200]
-                \\ stx %i2, [%l0 + 208]
-                \\ stx %i3, [%l0 + 216]
-                \\ stx %i4, [%l0 + 224]
-                \\ stx %i5, [%l0 + 232]
-                \\ stx %i6, [%l0 + 240]
-                \\ stx %i7, [%l0 + 248]
-                \\ call 1f
-                \\  stx %o7, [%l0 + 256]
-                \\1:
-            else
-                \\ std %g0, [%l0 + 0]
-                \\ std %g2, [%l0 + 8]
-                \\ std %g4, [%l0 + 16]
-                \\ std %g6, [%l0 + 24]
-                \\ std %o0, [%l0 + 32]
-                \\ std %o2, [%l0 + 40]
-                \\ std %o4, [%l0 + 48]
-                \\ std %o6, [%l0 + 56]
-                \\ std %l0, [%l0 + 64]
-                \\ std %l2, [%l0 + 72]
-                \\ std %l4, [%l0 + 80]
-                \\ std %l6, [%l0 + 88]
-                \\ std %i0, [%l0 + 96]
-                \\ std %i2, [%l0 + 104]
-                \\ std %i4, [%l0 + 112]
-                \\ std %i6, [%l0 + 120]
-                \\ call 1f
-                \\  st %o7, [%l0 + 128]
-                \\1:
-            :
-            : [gprs] "{l0}" (&ctx),
-            : .{ .o7 = true, .memory = true });
-        return ctx;
-    }
-
-    noinline fn flushWindows() void {
-        // Flush all register windows except the current one (hence `noinline`). This ensures that
-        // we actually see meaningful data on the stack when we walk the frame chain.
-        if (comptime builtin.target.cpu.has(.sparc, .v9))
-            asm volatile ("flushw" ::: .{ .memory = true })
-        else
-            asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
-    }
-
-    pub fn dwarfRegisterBytes(ctx: *Sparc, register_num: u16) DwarfRegisterError![]u8 {
-        switch (register_num) {
-            0...7 => return @ptrCast(&ctx.g[register_num]),
-            8...15 => return @ptrCast(&ctx.o[register_num - 8]),
-            16...23 => return @ptrCast(&ctx.l[register_num - 16]),
-            24...31 => return @ptrCast(&ctx.i[register_num - 24]),
-            32 => return @ptrCast(&ctx.pc),
-
-            else => return error.InvalidRegister,
-        }
-    }
-};
-
 /// This is an `extern struct` so that inline assembly in `current` can use field offsets.
 const Riscv = extern struct {
     /// The numbered general-purpose registers r0 - r31. r0 must be zero.
@@ -1334,7 +1107,7 @@ const Riscv = extern struct {
                 \\ sw ra, 128(t0)
                 \\ lw ra, 4(t0)
             :
-            : [gprs] "{t0}" (&ctx),
+            : [ctx] "{t0}" (&ctx),
             : .{ .memory = true });
         return ctx;
     }
@@ -1376,7 +1149,7 @@ const S390x = extern struct {
             \\ lg %%r0, 0(%%r2)
             \\ lg %%r1, 8(%%r2)
             :
-            : [gprs] "{r2}" (&ctx),
+            : [ctx] "{r2}" (&ctx),
             : .{ .memory = true });
         return ctx;
     }
@@ -1398,6 +1171,104 @@ const S390x = extern struct {
     }
 };
 
+/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
+const Sparc = extern struct {
+    g: [8]Gpr,
+    o: [8]Gpr,
+    l: [8]Gpr,
+    i: [8]Gpr,
+    pc: Gpr,
+
+    pub const Gpr = if (native_arch == .sparc64) u64 else u32;
+
+    pub inline fn current() Sparc {
+        flushWindows();
+
+        var ctx: Sparc = undefined;
+        asm volatile (if (Gpr == u64)
+                \\ stx %g0, [%l0 + 0]
+                \\ stx %g1, [%l0 + 8]
+                \\ stx %g2, [%l0 + 16]
+                \\ stx %g3, [%l0 + 24]
+                \\ stx %g4, [%l0 + 32]
+                \\ stx %g5, [%l0 + 40]
+                \\ stx %g6, [%l0 + 48]
+                \\ stx %g7, [%l0 + 56]
+                \\ stx %o0, [%l0 + 64]
+                \\ stx %o1, [%l0 + 72]
+                \\ stx %o2, [%l0 + 80]
+                \\ stx %o3, [%l0 + 88]
+                \\ stx %o4, [%l0 + 96]
+                \\ stx %o5, [%l0 + 104]
+                \\ stx %o6, [%l0 + 112]
+                \\ stx %o7, [%l0 + 120]
+                \\ stx %l0, [%l0 + 128]
+                \\ stx %l1, [%l0 + 136]
+                \\ stx %l2, [%l0 + 144]
+                \\ stx %l3, [%l0 + 152]
+                \\ stx %l4, [%l0 + 160]
+                \\ stx %l5, [%l0 + 168]
+                \\ stx %l6, [%l0 + 176]
+                \\ stx %l7, [%l0 + 184]
+                \\ stx %i0, [%l0 + 192]
+                \\ stx %i1, [%l0 + 200]
+                \\ stx %i2, [%l0 + 208]
+                \\ stx %i3, [%l0 + 216]
+                \\ stx %i4, [%l0 + 224]
+                \\ stx %i5, [%l0 + 232]
+                \\ stx %i6, [%l0 + 240]
+                \\ stx %i7, [%l0 + 248]
+                \\ call 1f
+                \\  stx %o7, [%l0 + 256]
+                \\1:
+            else
+                \\ std %g0, [%l0 + 0]
+                \\ std %g2, [%l0 + 8]
+                \\ std %g4, [%l0 + 16]
+                \\ std %g6, [%l0 + 24]
+                \\ std %o0, [%l0 + 32]
+                \\ std %o2, [%l0 + 40]
+                \\ std %o4, [%l0 + 48]
+                \\ std %o6, [%l0 + 56]
+                \\ std %l0, [%l0 + 64]
+                \\ std %l2, [%l0 + 72]
+                \\ std %l4, [%l0 + 80]
+                \\ std %l6, [%l0 + 88]
+                \\ std %i0, [%l0 + 96]
+                \\ std %i2, [%l0 + 104]
+                \\ std %i4, [%l0 + 112]
+                \\ std %i6, [%l0 + 120]
+                \\ call 1f
+                \\  st %o7, [%l0 + 128]
+                \\1:
+            :
+            : [ctx] "{l0}" (&ctx),
+            : .{ .o7 = true, .memory = true });
+        return ctx;
+    }
+
+    noinline fn flushWindows() void {
+        // Flush all register windows except the current one (hence `noinline`). This ensures that
+        // we actually see meaningful data on the stack when we walk the frame chain.
+        if (comptime builtin.target.cpu.has(.sparc, .v9))
+            asm volatile ("flushw" ::: .{ .memory = true })
+        else
+            asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *Sparc, register_num: u16) DwarfRegisterError![]u8 {
+        switch (register_num) {
+            0...7 => return @ptrCast(&ctx.g[register_num]),
+            8...15 => return @ptrCast(&ctx.o[register_num - 8]),
+            16...23 => return @ptrCast(&ctx.l[register_num - 16]),
+            24...31 => return @ptrCast(&ctx.i[register_num - 24]),
+            32 => return @ptrCast(&ctx.pc),
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
 /// This is an `extern struct` so that inline assembly in `current` can use field offsets.
 const Ve = extern struct {
     s: [64]u64,
@@ -1492,6 +1363,135 @@ const Ve = extern struct {
     }
 };
 
+const X86 = struct {
+    /// The first 8 registers here intentionally match the order of registers in the x86 instruction
+    /// encoding. This order is inherited by the PUSHA instruction and the DWARF register mappings,
+    /// among other things.
+    pub const Gpr = enum {
+        // zig fmt: off
+        eax, ecx, edx, ebx,
+        esp, ebp, esi, edi,
+        eip,
+        // zig fmt: on
+    };
+    gprs: std.enums.EnumArray(Gpr, u32),
+
+    pub inline fn current() X86 {
+        var ctx: X86 = undefined;
+        asm volatile (
+            \\ movl %%eax, 0x00(%%edi)
+            \\ movl %%ecx, 0x04(%%edi)
+            \\ movl %%edx, 0x08(%%edi)
+            \\ movl %%ebx, 0x0c(%%edi)
+            \\ movl %%esp, 0x10(%%edi)
+            \\ movl %%ebp, 0x14(%%edi)
+            \\ movl %%esi, 0x18(%%edi)
+            \\ movl %%edi, 0x1c(%%edi)
+            \\ call 1f
+            \\1:
+            \\ popl 0x20(%%edi)
+            :
+            : [gprs] "{edi}" (&ctx.gprs.values),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *X86, register_num: u16) DwarfRegisterError![]u8 {
+        // System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1
+        //   § 2.4.2 "DWARF Register Number Mapping"
+        switch (register_num) {
+            // The order of `Gpr` intentionally matches DWARF's mappings.
+            //
+            // x86-macos sometimes uses different mappings (ebp and esp are reversed when the unwind
+            // information is from `__eh_frame`). This deviation is not considered here, because
+            // x86-macos is a deprecated target which is not supported by the Zig Standard Library.
+            0...8 => return @ptrCast(&ctx.gprs.values[register_num]),
+
+            9 => return error.UnsupportedRegister, // eflags
+            11...18 => return error.UnsupportedRegister, // st0 - st7
+            21...28 => return error.UnsupportedRegister, // xmm0 - xmm7
+            29...36 => return error.UnsupportedRegister, // mm0 - mm7
+            39 => return error.UnsupportedRegister, // mxcsr
+            40...45 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs
+            48 => return error.UnsupportedRegister, // tr
+            49 => return error.UnsupportedRegister, // ldtr
+            93...100 => return error.UnsupportedRegister, // k0 - k7 (AVX-512)
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
+const X86_64 = struct {
+    /// The order here intentionally matches the order of the DWARF register mappings. It's unclear
+    /// where those mappings actually originated from---the ordering of the first 4 registers seems
+    /// quite unusual---but it is currently convenient for us to match DWARF.
+    pub const Gpr = enum {
+        // zig fmt: off
+        rax, rdx, rcx, rbx,
+        rsi, rdi, rbp, rsp,
+        r8,  r9,  r10, r11,
+        r12, r13, r14, r15,
+        rip,
+        // zig fmt: on
+    };
+    gprs: std.enums.EnumArray(Gpr, u64),
+
+    pub inline fn current() X86_64 {
+        var ctx: X86_64 = undefined;
+        asm volatile (
+            \\ movq %%rax, 0x00(%%rdi)
+            \\ movq %%rdx, 0x08(%%rdi)
+            \\ movq %%rcx, 0x10(%%rdi)
+            \\ movq %%rbx, 0x18(%%rdi)
+            \\ movq %%rsi, 0x20(%%rdi)
+            \\ movq %%rdi, 0x28(%%rdi)
+            \\ movq %%rbp, 0x30(%%rdi)
+            \\ movq %%rsp, 0x38(%%rdi)
+            \\ movq %%r8,  0x40(%%rdi)
+            \\ movq %%r9,  0x48(%%rdi)
+            \\ movq %%r10, 0x50(%%rdi)
+            \\ movq %%r11, 0x58(%%rdi)
+            \\ movq %%r12, 0x60(%%rdi)
+            \\ movq %%r13, 0x68(%%rdi)
+            \\ movq %%r14, 0x70(%%rdi)
+            \\ movq %%r15, 0x78(%%rdi)
+            \\ leaq (%%rip), %%rax
+            \\ movq %%rax, 0x80(%%rdi)
+            \\ movq 0x00(%%rdi), %%rax
+            :
+            : [gprs] "{rdi}" (&ctx.gprs.values),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *X86_64, register_num: u16) DwarfRegisterError![]u8 {
+        // System V Application Binary Interface AMD64 Architecture Processor Supplement
+        //   § 3.6.2 "DWARF Register Number Mapping"
+        switch (register_num) {
+            // The order of `Gpr` intentionally matches DWARF's mappings.
+            0...16 => return @ptrCast(&ctx.gprs.values[register_num]),
+
+            17...32 => return error.UnsupportedRegister, // xmm0 - xmm15
+            33...40 => return error.UnsupportedRegister, // st0 - st7
+            41...48 => return error.UnsupportedRegister, // mm0 - mm7
+            49 => return error.UnsupportedRegister, // rflags
+            50...55 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs
+            58...59 => return error.UnsupportedRegister, // fs.base, gs.base
+            62 => return error.UnsupportedRegister, // tr
+            63 => return error.UnsupportedRegister, // ldtr
+            64 => return error.UnsupportedRegister, // mxcsr
+            65 => return error.UnsupportedRegister, // fcw
+            66 => return error.UnsupportedRegister, // fsw
+            67...82 => return error.UnsupportedRegister, // xmm16 - xmm31 (AVX-512)
+            118...125 => return error.UnsupportedRegister, // k0 - k7 (AVX-512)
+            130...145 => return error.UnsupportedRegister, // r16 - r31 (APX)
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
 /// The native operating system's `ucontext_t` as seen in the third argument to signal handlers.
 ///
 /// These are dramatically simplified since we only need general-purpose registers and don't care