Fix PowerPC syscalls causing invalid code from CBE

Fixes #25209.

On PowerPC, some registers are both inputs to syscalls and clobbered by
them. An example is r0, which initially contains the syscall number, but
may be overwritten during execution of the syscall.

musl and glibc use a `+` (read-write) constraint to indicate this, which
isn't supported in Zig. The current implementation of PowerPC syscalls
in the Zig standard library instead lists these registers as both inputs
and clobbers, but this results in the C backend generating code that is
invalid for at least some C compilers, like GCC, which doesn't support
the specifying the same register as both an input and a clobber.

This PR changes the PowerPC syscall functions to list such registers as
inputs and outputs rather than inputs and clobbers. Thanks to jacobly0
who pointed out that it's possible to have multiple outputs; I had
gotten the wrong idea from the documentation.
This commit is contained in:
taylor.fish 2025-09-13 17:17:18 -07:00 committed by Alex Rønne Petersen
parent 1f0cf7c551
commit 427f0025db
No known key found for this signature in database
2 changed files with 122 additions and 14 deletions

View file

@ -15,84 +15,125 @@ const sockaddr = linux.sockaddr;
const timespec = linux.timespec; const timespec = linux.timespec;
pub fn syscall0(number: SYS) usize { pub fn syscall0(number: SYS) usize {
// r0 is both an input register and a clobber. musl and glibc achieve this with
// a "+" constraint, which isn't supported in Zig, so instead we separately list
// r0 as both an input and an output. (Listing it as an input and a clobber would
// cause the C backend to emit invalid code; see #25209.)
var r0_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall1(number: SYS, arg1: usize) usize { pub fn syscall1(number: SYS, arg1: usize) usize {
// r0 is both an input and a clobber.
var r0_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
[arg3] "{r5}" (arg3), [arg3] "{r5}" (arg3),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize { pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
var r6_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
[r6_out] "={r6}" (r6_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
[arg3] "{r5}" (arg3), [arg3] "{r5}" (arg3),
[arg4] "{r6}" (arg4), [arg4] "{r6}" (arg4),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize { pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
var r6_out: usize = undefined;
var r7_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
[r6_out] "={r6}" (r6_out),
[r7_out] "={r7}" (r7_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
[arg3] "{r5}" (arg3), [arg3] "{r5}" (arg3),
[arg4] "{r6}" (arg4), [arg4] "{r6}" (arg4),
[arg5] "{r7}" (arg5), [arg5] "{r7}" (arg5),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall6( pub fn syscall6(
@ -104,12 +145,25 @@ pub fn syscall6(
arg5: usize, arg5: usize,
arg6: usize, arg6: usize,
) usize { ) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
var r6_out: usize = undefined;
var r7_out: usize = undefined;
var r8_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
[r6_out] "={r6}" (r6_out),
[r7_out] "={r7}" (r7_out),
[r8_out] "={r8}" (r8_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
@ -117,7 +171,7 @@ pub fn syscall6(
[arg4] "{r6}" (arg4), [arg4] "{r6}" (arg4),
[arg5] "{r7}" (arg5), [arg5] "{r7}" (arg5),
[arg6] "{r8}" (arg6), [arg6] "{r8}" (arg6),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn clone() callconv(.naked) usize { pub fn clone() callconv(.naked) usize {

View file

@ -15,84 +15,125 @@ const sockaddr = linux.sockaddr;
const timespec = linux.timespec; const timespec = linux.timespec;
pub fn syscall0(number: SYS) usize { pub fn syscall0(number: SYS) usize {
// r0 is both an input register and a clobber. musl and glibc achieve this with
// a "+" constraint, which isn't supported in Zig, so instead we separately list
// r0 as both an input and an output. (Listing it as an input and a clobber would
// cause the C backend to emit invalid code; see #25209.)
var r0_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall1(number: SYS, arg1: usize) usize { pub fn syscall1(number: SYS, arg1: usize) usize {
// r0 is both an input and a clobber.
var r0_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
[arg3] "{r5}" (arg3), [arg3] "{r5}" (arg3),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize { pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
var r6_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
[r6_out] "={r6}" (r6_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
[arg3] "{r5}" (arg3), [arg3] "{r5}" (arg3),
[arg4] "{r6}" (arg4), [arg4] "{r6}" (arg4),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize { pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
var r6_out: usize = undefined;
var r7_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
[r6_out] "={r6}" (r6_out),
[r7_out] "={r7}" (r7_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
[arg3] "{r5}" (arg3), [arg3] "{r5}" (arg3),
[arg4] "{r6}" (arg4), [arg4] "{r6}" (arg4),
[arg5] "{r7}" (arg5), [arg5] "{r7}" (arg5),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn syscall6( pub fn syscall6(
@ -104,12 +145,25 @@ pub fn syscall6(
arg5: usize, arg5: usize,
arg6: usize, arg6: usize,
) usize { ) usize {
// These registers are both inputs and clobbers.
var r0_out: usize = undefined;
var r4_out: usize = undefined;
var r5_out: usize = undefined;
var r6_out: usize = undefined;
var r7_out: usize = undefined;
var r8_out: usize = undefined;
return asm volatile ( return asm volatile (
\\ sc \\ sc
\\ bns+ 1f \\ bns+ 1f
\\ neg 3, 3 \\ neg 3, 3
\\ 1: \\ 1:
: [ret] "={r3}" (-> usize), : [ret] "={r3}" (-> usize),
[r0_out] "={r0}" (r0_out),
[r4_out] "={r4}" (r4_out),
[r5_out] "={r5}" (r5_out),
[r6_out] "={r6}" (r6_out),
[r7_out] "={r7}" (r7_out),
[r8_out] "={r8}" (r8_out),
: [number] "{r0}" (@intFromEnum(number)), : [number] "{r0}" (@intFromEnum(number)),
[arg1] "{r3}" (arg1), [arg1] "{r3}" (arg1),
[arg2] "{r4}" (arg2), [arg2] "{r4}" (arg2),
@ -117,7 +171,7 @@ pub fn syscall6(
[arg4] "{r6}" (arg4), [arg4] "{r6}" (arg4),
[arg5] "{r7}" (arg5), [arg5] "{r7}" (arg5),
[arg6] "{r8}" (arg6), [arg6] "{r8}" (arg6),
: .{ .memory = true, .cr0 = true, .r0 = true, .r4 = true, .r5 = true, .r6 = true, .r7 = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true }); : .{ .memory = true, .cr0 = true, .r9 = true, .r10 = true, .r11 = true, .r12 = true, .ctr = true, .xer = true });
} }
pub fn clone() callconv(.naked) usize { pub fn clone() callconv(.naked) usize {