authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-03 07:42:17+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-03 07:42:17+02:00
log1f083e9ed78f5c3c2d848d0abc58612c4ce88804
tree7eb2c831d2eecdd768c847c9ffe7948de7f2d24f
parentbc4da9a90743c11c7c0b3e485f46d365d57d87b7
parent95bdb0c1c65c128923ffac3f4be6b4619eb4a54b
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25443 from alexrp/s390x-unwind

`std.debug`: add `s390x-linux` unwind support

8 files changed, 124 insertions(+), 10 deletions(-)

lib/std/debug.zig+3
...@@ -952,6 +952,9 @@ const StackIterator = union(enum) {...@@ -952,6 +952,9 @@ const StackIterator = union(enum) {
952 /// Offset of the saved return address wrt the frame pointer.952 /// Offset of the saved return address wrt the frame pointer.
953 const ra_offset = off: {953 const ra_offset = off: {
954 if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize);954 if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize);
955 // On s390x, r14 is the link register and we need to grab it from its customary slot in the
956 // register save area (ELF ABI s390x Supplement §1.2.2.2).
957 if (native_arch == .s390x) break :off 14 * @sizeOf(usize);
955 break :off @sizeOf(usize);958 break :off @sizeOf(usize);
956 };959 };
957960
lib/std/debug/Dwarf.zig+3
...@@ -1433,6 +1433,7 @@ pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {...@@ -1433,6 +1433,7 @@ pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
1433 .arm, .armeb, .thumb, .thumbeb => 15,1433 .arm, .armeb, .thumb, .thumbeb => 15,
1434 .loongarch32, .loongarch64 => 32,1434 .loongarch32, .loongarch64 => 32,
1435 .riscv32, .riscv32be, .riscv64, .riscv64be => 32,1435 .riscv32, .riscv32be, .riscv64, .riscv64be => 32,
1436 .s390x => 65,
1436 .x86 => 8,1437 .x86 => 8,
1437 .x86_64 => 16,1438 .x86_64 => 16,
1438 else => null,1439 else => null,
...@@ -1445,6 +1446,7 @@ pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {...@@ -1445,6 +1446,7 @@ pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
1445 .arm, .armeb, .thumb, .thumbeb => 11,1446 .arm, .armeb, .thumb, .thumbeb => 11,
1446 .loongarch32, .loongarch64 => 22,1447 .loongarch32, .loongarch64 => 22,
1447 .riscv32, .riscv32be, .riscv64, .riscv64be => 8,1448 .riscv32, .riscv32be, .riscv64, .riscv64be => 8,
1449 .s390x => 11,
1448 .x86 => 5,1450 .x86 => 5,
1449 .x86_64 => 6,1451 .x86_64 => 6,
1450 else => unreachable,1452 else => unreachable,
...@@ -1457,6 +1459,7 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {...@@ -1457,6 +1459,7 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
1457 .arm, .armeb, .thumb, .thumbeb => 13,1459 .arm, .armeb, .thumb, .thumbeb => 13,
1458 .loongarch32, .loongarch64 => 3,1460 .loongarch32, .loongarch64 => 3,
1459 .riscv32, .riscv32be, .riscv64, .riscv64be => 2,1461 .riscv32, .riscv32be, .riscv64, .riscv64be => 2,
1462 .s390x => 15,
1460 .x86 => 4,1463 .x86 => 4,
1461 .x86_64 => 7,1464 .x86_64 => 7,
1462 else => unreachable,1465 else => unreachable,
lib/std/debug/Dwarf/SelfUnwinder.zig+12-4
...@@ -176,7 +176,11 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE...@@ -176,7 +176,11 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
176 break :cfa try applyOffset(ptr.*, ro.offset);176 break :cfa try applyOffset(ptr.*, ro.offset);
177 },177 },
178 .expression => |expr| cfa: {178 .expression => |expr| cfa: {
179 // On all implemented architectures, the CFA is defined to be the previous frame's SP179 // On most implemented architectures, the CFA is defined to be the previous frame's SP.
180 //
181 // On s390x, it's defined to be SP + 160 (ELF ABI s390x Supplement §1.6.3); however,
182 // what this actually means is that there will be a `def_cfa r15 + 160`, so nothing
183 // special for us to do.
180 const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*;184 const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*;
181 unwinder.expr_vm.reset();185 unwinder.expr_vm.reset();
182 const value = try unwinder.expr_vm.run(expr, gpa, .{186 const value = try unwinder.expr_vm.run(expr, gpa, .{
...@@ -193,9 +197,13 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE...@@ -193,9 +197,13 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
193 // If unspecified, we'll use the default rule for the return address register, which is197 // If unspecified, we'll use the default rule for the return address register, which is
194 // typically equivalent to `.undefined` (meaning there is no return address), but may be198 // typically equivalent to `.undefined` (meaning there is no return address), but may be
195 // overriden by ABIs.199 // overriden by ABIs.
196 var has_return_address: bool = builtin.cpu.arch.isAARCH64() and200 var has_return_address: bool = switch (builtin.cpu.arch) {
197 return_address_register >= 19 and201 // DWARF for the Arm 64-bit Architecture (AArch64) §4.3, p1
198 return_address_register <= 28;202 .aarch64, .aarch64_be => return_address_register >= 19 and return_address_register <= 28,
203 // ELF ABI s390x Supplement §1.6.4
204 .s390x => return_address_register >= 6 and return_address_register <= 15,
205 else => false,
206 };
199207
200 // Create a copy of the CPU state, to which we will apply the new rules.208 // Create a copy of the CPU state, to which we will apply the new rules.
201 var new_cpu_state = unwinder.cpu_state;209 var new_cpu_state = unwinder.cpu_state;
lib/std/debug/SelfInfo/Elf.zig+1
...@@ -90,6 +90,7 @@ pub const can_unwind: bool = s: {...@@ -90,6 +90,7 @@ pub const can_unwind: bool = s: {
90 .loongarch64,90 .loongarch64,
91 .riscv32,91 .riscv32,
92 .riscv64,92 .riscv64,
93 .s390x,
93 .x86,94 .x86,
94 .x86_64,95 .x86_64,
95 },96 },
lib/std/debug/cpu_context.zig+87
...@@ -8,6 +8,7 @@ else switch (native_arch) {...@@ -8,6 +8,7 @@ else switch (native_arch) {
8 .arm, .armeb, .thumb, .thumbeb => Arm,8 .arm, .armeb, .thumb, .thumbeb => Arm,
9 .loongarch32, .loongarch64 => LoongArch,9 .loongarch32, .loongarch64 => LoongArch,
10 .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv,10 .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv,
11 .s390x => S390x,
11 .x86 => X86,12 .x86 => X86,
12 .x86_64 => X86_64,13 .x86_64 => X86_64,
13 else => noreturn,14 else => noreturn,
...@@ -189,6 +190,17 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {...@@ -189,6 +190,17 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
189 },190 },
190 else => null,191 else => null,
191 },192 },
193 .s390x => switch (builtin.os.tag) {
194 .linux => .{
195 .r = uc.mcontext.gregs,
196 .f = uc.mcontext.fregs,
197 .psw = .{
198 .mask = uc.mcontext.psw.mask,
199 .addr = uc.mcontext.psw.addr,
200 },
201 },
202 else => null,
203 },
192 else => null,204 else => null,
193 };205 };
194}206}
...@@ -677,6 +689,81 @@ pub const Riscv = extern struct {...@@ -677,6 +689,81 @@ pub const Riscv = extern struct {
677 }689 }
678};690};
679691
692/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
693pub const S390x = extern struct {
694 /// The numbered general-purpose registers r0 - r15.
695 r: [16]u64,
696 /// The numbered floating-point registers f0 - f15. Yes, really - they can be used in DWARF CFI.
697 f: [16]f64,
698 /// The program counter.
699 psw: extern struct {
700 mask: u64,
701 addr: u64,
702 },
703
704 pub inline fn current() S390x {
705 var ctx: S390x = undefined;
706 asm volatile (
707 \\ stmg %%r0, %%r15, 0(%%r2)
708 \\ std %%f0, 128(%%r2)
709 \\ std %%f1, 136(%%r2)
710 \\ std %%f2, 144(%%r2)
711 \\ std %%f3, 152(%%r2)
712 \\ std %%f4, 160(%%r2)
713 \\ std %%f5, 168(%%r2)
714 \\ std %%f6, 176(%%r2)
715 \\ std %%f7, 184(%%r2)
716 \\ std %%f8, 192(%%r2)
717 \\ std %%f9, 200(%%r2)
718 \\ std %%f10, 208(%%r2)
719 \\ std %%f11, 216(%%r2)
720 \\ std %%f12, 224(%%r2)
721 \\ std %%f13, 232(%%r2)
722 \\ std %%f14, 240(%%r2)
723 \\ std %%f15, 248(%%r2)
724 \\ epsw %%r0, %%r1
725 \\ stm %%r0, %%r1, 256(%%r2)
726 \\ larl %%r0, .
727 \\ stg %%r0, 264(%%r2)
728 \\ lg %%r0, 0(%%r2)
729 \\ lg %%r1, 8(%%r2)
730 :
731 : [gprs] "{r2}" (&ctx),
732 : .{ .memory = true });
733 return ctx;
734 }
735
736 pub fn dwarfRegisterBytes(ctx: *S390x, register_num: u16) DwarfRegisterError![]u8 {
737 switch (register_num) {
738 0...15 => return @ptrCast(&ctx.r[register_num]),
739 // Why???
740 16 => return @ptrCast(&ctx.f[0]),
741 17 => return @ptrCast(&ctx.f[2]),
742 18 => return @ptrCast(&ctx.f[4]),
743 19 => return @ptrCast(&ctx.f[6]),
744 20 => return @ptrCast(&ctx.f[1]),
745 21 => return @ptrCast(&ctx.f[3]),
746 22 => return @ptrCast(&ctx.f[5]),
747 23 => return @ptrCast(&ctx.f[7]),
748 24 => return @ptrCast(&ctx.f[8]),
749 25 => return @ptrCast(&ctx.f[10]),
750 26 => return @ptrCast(&ctx.f[12]),
751 27 => return @ptrCast(&ctx.f[14]),
752 28 => return @ptrCast(&ctx.f[9]),
753 29 => return @ptrCast(&ctx.f[11]),
754 30 => return @ptrCast(&ctx.f[13]),
755 31 => return @ptrCast(&ctx.f[15]),
756 64 => return @ptrCast(&ctx.psw.mask),
757 65 => return @ptrCast(&ctx.psw.addr),
758
759 48...63 => return error.UnsupportedRegister, // a0 - a15
760 68...83 => return error.UnsupportedRegister, // v16 - v31
761
762 else => return error.InvalidRegister,
763 }
764 }
765};
766
680const signal_ucontext_t = switch (native_os) {767const signal_ucontext_t = switch (native_os) {
681 .linux => std.os.linux.ucontext_t,768 .linux => std.os.linux.ucontext_t,
682 .emscripten => std.os.emscripten.ucontext_t,769 .emscripten => std.os.emscripten.ucontext_t,
lib/std/os/linux/s390x.zig+8-3
...@@ -269,7 +269,12 @@ pub const ucontext_t = extern struct {...@@ -269,7 +269,12 @@ pub const ucontext_t = extern struct {
269};269};
270270
271pub const mcontext_t = extern struct {271pub const mcontext_t = extern struct {
272 __regs1: [18]u64,272 psw: extern struct {
273 __regs2: [18]u32,273 mask: u64,
274 __regs3: [16]f64,274 addr: u64,
275 },
276 gregs: [16]u64,
277 aregs: [16]u32,
278 fpc: u32,
279 fregs: [16]f64,
275};280};
src/Compilation.zig+6-2
...@@ -7193,6 +7193,9 @@ pub fn addCCArgs(...@@ -7193,6 +7193,9 @@ pub fn addCCArgs(
7193 }7193 }
71947194
7195 try argv.append(if (mod.omit_frame_pointer) "-fomit-frame-pointer" else "-fno-omit-frame-pointer");7195 try argv.append(if (mod.omit_frame_pointer) "-fomit-frame-pointer" else "-fno-omit-frame-pointer");
7196 if (target.cpu.arch == .s390x) {
7197 try argv.append(if (mod.omit_frame_pointer) "-mbackchain" else "-mno-backchain");
7198 }
71967199
7197 const ssp_buf_size = mod.stack_protector;7200 const ssp_buf_size = mod.stack_protector;
7198 if (ssp_buf_size != 0) {7201 if (ssp_buf_size != 0) {
...@@ -7258,9 +7261,10 @@ pub fn addCCArgs(...@@ -7258,9 +7261,10 @@ pub fn addCCArgs(
7258 const is_enabled = target.cpu.features.isEnabled(index);7261 const is_enabled = target.cpu.features.isEnabled(index);
72597262
7260 if (feature.llvm_name) |llvm_name| {7263 if (feature.llvm_name) |llvm_name| {
7261 // We communicate float ABI to Clang through the dedicated options.7264 // We communicate these to Clang through the dedicated options.
7262 if (std.mem.startsWith(u8, llvm_name, "soft-float") or7265 if (std.mem.startsWith(u8, llvm_name, "soft-float") or
7263 std.mem.startsWith(u8, llvm_name, "hard-float"))7266 std.mem.startsWith(u8, llvm_name, "hard-float") or
7267 (target.cpu.arch == .s390x and std.mem.eql(u8, llvm_name, "backchain")))
7264 continue;7268 continue;
72657269
7266 // Ignore these until we figure out how to handle the concept of omitting features.7270 // Ignore these until we figure out how to handle the concept of omitting features.
src/Package/Module.zig+4-1
...@@ -343,7 +343,10 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {...@@ -343,7 +343,10 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
343 // See https://github.com/ziglang/zig/issues/23539343 // See https://github.com/ziglang/zig/issues/23539
344 if (target_util.isDynamicAMDGCNFeature(target, feature)) continue;344 if (target_util.isDynamicAMDGCNFeature(target, feature)) continue;
345345
346 const is_enabled = target.cpu.features.isEnabled(feature.index);346 var is_enabled = target.cpu.features.isEnabled(feature.index);
347 if (target.cpu.arch == .s390x and @as(std.Target.s390x.Feature, @enumFromInt(feature.index)) == .backchain) {
348 is_enabled = !omit_frame_pointer;
349 }
347350
348 if (is_enabled) {351 if (is_enabled) {
349 try buf.ensureUnusedCapacity(2 + llvm_name.len);352 try buf.ensureUnusedCapacity(2 + llvm_name.len);