macOS arm64: std::mem::alloc fails in optimized builds (arm64 syscall wrappers do not declare x1 as clobbered)
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #958 by @LunaStev — merged
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- rust
- Domain
- operating-systems
Research direction
Start with std/sys/macos/arm64/syscall.wave and compare its syscall wrappers with std/sys/macos/amd64/syscall.wave, which the issue cites. Declare x1 as an output in syscall0 through syscall6, then verify optimized macOS arm64 builds with the supplied allocation reproducer at -O1 or higher; done means the allocations succeed without corrupting state. The issue also points to other Darwin arm64 wrappers for a possible later audit.
Written by the indexing model from the issue text.
Description
On macOS arm64, std::mem::alloc fails (returns null) or corrupts state in optimized builds (-O1 and up), because the arm64 syscall wrappers do not declare that XNU overwrites x1 with the secondary return value. The x86_64 wrappers already handle the same thing for rdx (see the comment in std/sys/macos/amd64/syscall.wave).
Repro
import("std::mem::alloc")::{mem_alloc, mem_free};
extern(c, "clock") fun c_clock() -> i64;
fun main() -> i32 {
// A size only known at run time, used for three allocations in a row.
var n: i64 = 1520 + (c_clock() % 2);
var a: ptr<u8> = mem_alloc(n * 8);
var b: ptr<u8> = mem_alloc(n * 8);
var c: ptr<u8> = mem_alloc((n + 1) * 8);
if (a == null || b == null || c == null) {
println("out of memory for {} entries", n);
return 1;
}
println("ok {}", n);
mem_free(a, n * 8);
mem_free(b, n * 8);
mem_free(c, (n + 1) * 8);
return 0;
}
Result on a GitHub macos-26 (Apple Silicon) runner, wavec 0.2.1-pre-beta, aarch64-apple-darwin release:
| flags | result |
|---|---|
-O0 |
ok 1520 |
-O1, -O2, -O3 |
out of memory for 1520 entries (exit 1) |
I did not run this exact program elsewhere, but the larger programs below build with -O2 and pass on macos-26-intel (x86_64), Linux x64 and Linux arm64. In those larger programs (the examples of https://github.com/potatosl/wjson) the same bug showed up as null from mem_alloc in a CSV example at -O1 and above, and as a segmentation fault (exit 139) in a JSON editing example at -O2 and above. All of them pass at -O0.
Cause (my reading, confirmed by the fix below)
std/sys/macos/arm64/syscall.wave declares the inputs x16, x0..x5 and the output x0, and nothing else. XNU's arm64 syscall return writes x1 as well. With inlining, the compiler assumes x1 still holds the second argument (here the mmap length) after the svc, reuses that value for the next call, and the next mmap gets length 0 or garbage and fails. std/sys/macos/amd64/syscall.wave has this comment for rdx and declares out("rdx") secondary in every wrapper.
Fix
Declare x1 as an output in every arm64 wrapper (syscall0 .. syscall6):
pub fun syscall2(id: i64, a1: i64, a2: i64) -> i64 {
var ret: i64;
var secondary: i64;
asm {
"svc #0x80\ncneg x0, x0, cs"
in("x16") id
in("x0") a1
in("x1") a2
out("x0") ret
out("x1") secondary
}
return ret;
}
Verified on the same runner by patching all 7 wrappers in a copy of std and passing it with --std-root:
| flags | result |
|---|---|
-O1, -O2, -O3 |
ok 1520 / ok 1521, exit 0 |
The two larger examples (edit_config: segfault, csv2json: out of memory at -O1/-O2/-O3 with the stock std) also pass at -O2 and -O3 with the patched std.
Other wrappers that use the same raw-syscall pattern on Darwin arm64 (fs.wave, process.wave, time.wave, ...) may have the same gap; I only checked memory. Related audit: #434.
- Dominant language
- Rust
- Stars
- 56
- Forks
- 21
- Avg merge
- 4h 58m
- Merged PRs (30d)
- 54
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from wavefnd/Wave
-
bug good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
Maintainers usually reply within 1 day
-
bug good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
good first issue performance
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
Maintainers usually reply within 1 day
-
enhancement good first issue needs testing
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
bug good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 3 days
-
Zellij doesn't validate hex colors correctly and crashesPossibly taken @ymcx claimed this today. Opensuspected bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zellij-org/zellij#5732 ·
Maintainers usually reply within 1 day
-
bug rust
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
jonathanong/no-mistakes#2267 ·
Maintainers usually reply within 1 day
-
BASEPRI needs an ISBOpen
Difficulty 2/5 1-3 hours Newbie friendliness 69/100
rust-embedded/cortex-m#702 · 1 comment ·
-
Make major, minor, and makedev safe on solarish targetsPossibly taken @xtqqczze claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day