Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

macOS arm64: std::mem::alloc fails in optimized builds (arm64 syscall wrappers do not declare x1 as clobbered)

Closed Beginner friendly
#933 0 comments 0 reactions 0 assignees View on GitHub

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

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from wavefnd/Wave

All issues in wavefnd/Wave

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.