Skip to content

Support building the aarch64 cross-compiler on macOS - #171

Draft
samoht wants to merge 3 commits into
mirage:mainfrom
samoht:macos-cross
Draft

Support building the aarch64 cross-compiler on macOS#171
samoht wants to merge 3 commits into
mirage:mainfrom
samoht:macos-cross

Conversation

@samoht

@samoht samoht commented Jun 29, 2026

Copy link
Copy Markdown
Member

I'd like to cross-compile aarch64 Solo5/MirageOS unikernels on an Apple Silicon Mac.

This adds a macos/arm64 target to ocaml-solo5-cross-aarch64. The OCaml cross build runs through the llvm- toolprefix; a new --toolchain-use-absolute-path configure option pins the keg-only Homebrew llvm tools (off PATH when the cross compiler is later used) to absolute paths, while every other host keeps bare, relocatable tool names. The nolibc/openlibm archives are built with the toolprefix ar/ranlib (Apple's cannot index ELF), and solo5 is dropped from the macOS dependencies (only the cross toolchain and headers from solo5-cross-aarch64 are needed there). A CI job builds the cross-compiler on macos-latest.

The first two commits are #177 (printf as double + drop -lgcc), the libgcc-free change this builds on; they fall out once #177 merges. Per @shym's review I also dropped the -Wno-asm-operand-widths workaround, which #179 (openlibm v0.8.7) makes unnecessary — so the macOS CI job here stays red until #179 lands, then it is clean.

Companion: Solo5/solo5#641 builds solo5-cross-aarch64 on macOS.

Supersedes #168.

@dinosaure

Copy link
Copy Markdown
Member

We could split this pull request into several parts, including one concerning nolibc and the changes relating to double/long double. It would be particularly useful to put this change into context, as it may have implications for the generated assembly code.

A conservative approach would be to apply the change only to macOS, where it appears that double and long double have the same size and representation (if I am to go by this Wikipedia article, which is a rabbit hole in itself).

On some PowerPC systems,[12] long double is implemented as a double-double arithmetic, where a long double value is regarded as the exact sum of two double-precision values, giving at least a 106-bit precision; with such a format, the long double type does not conform to the IEEE floating-point standard. Otherwise, long double is simply a synonym for double (double precision), e.g. on 32-bit ARM,[13] 64-bit ARM (AArch64) (on Windows[14] and macOS[15]) and on 32-bit MIPS[16] (old ABI, a.k.a. o32).

We can then properly iterate through the rest (the configure.sh script and the CI).

samoht added a commit to samoht/solo5 that referenced this pull request Jul 6, 2026
Add a macOS/arm64 host case: configure.sh accepts a clang / ld.lld /
llvm-objcopy ELF cross toolchain and resolves the keg-only Homebrew
llvm-objcopy to an absolute path. solo5-cross-aarch64 cross-compiles with those
tools, declares the llvm + lld depexts, and a CI job builds it on macos-latest.

The toolchain is pure LLVM: no libgcc is bundled. The downstream link needs
none: ocaml-solo5's nolibc no longer pulls long double soft-float and its
wrapper does not pass -lgcc (mirage/ocaml-solo5#171).
samoht added a commit to samoht/solo5 that referenced this pull request Jul 6, 2026
Add a macOS/arm64 host case: configure.sh accepts a clang / ld.lld /
llvm-objcopy ELF cross toolchain and resolves the keg-only Homebrew
llvm-objcopy to an absolute path. solo5-cross-aarch64 cross-compiles with those
tools, declares the llvm + lld depexts, and a CI job builds it on macos-latest.

The toolchain is pure LLVM: no libgcc is bundled. The downstream link needs
none: ocaml-solo5's nolibc no longer pulls long double soft-float and its
wrapper does not pass -lgcc (mirage/ocaml-solo5#171).
@samoht

samoht commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Thanks, I'll split the nolibc double/long double change into its own PR with the context.

One clarification on the conservative approach: this is a property of the target, not the macOS host. The cross targets aarch64-linux-gnu (via clang -target), where long double is IEEE binary128 (quad), so printf pulls in __extenddftf2/__trunctfdf2 regardless of whether we build on macOS or Linux. On Linux the cross toolchain is gcc and libgcc supplies those routines; the LLVM-only macOS toolchain has no libgcc, which is why the link fails there.

Since it's really "nolibc's printf shouldn't require quad soft-float" (useful to any freestanding aarch64 target, not only macOS), I'd lean to keeping it unconditional, but I'm happy to gate it to the macOS build if you'd prefer. Then we can iterate on configure.sh and the CI.

@shym shym left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I trust CI builds, I think that #179 would avoid the need for the no-asm-operand-widths fix.

For the changes in gen_toolchain_tool.sh, I think I’m missing something: the resolved variable allow to work around the fact that some tools are in PATH during the build of the OCaml/Solo5 package but they won’t be later when using the cross compiler. Is that it? If so, why is that happening?
If that’s indeed the case, I’d suggest adding a configuration option, for instance --toolchain-use-absolute-path, that would enable that behaviour only when needed (I’m thinking of all the efforts put into avoiding absolute paths in OCaml, so I’d try to avoid absolute paths to come back when not absolutely needed (pun intended)).

Comment thread configure.sh Outdated
Comment on lines +81 to +91
if ! command -v "${MAKECONF_TOOLPREFIX}ar" >/dev/null 2>&1; then
for bindir in \
"$(command -v brew >/dev/null 2>&1 && brew --prefix llvm 2>/dev/null)/bin" \
/opt/homebrew/opt/llvm/bin /usr/local/opt/llvm/bin \
/opt/local/libexec/llvm-*/bin; do
if [ -x "${bindir}/${MAKECONF_TOOLPREFIX}ar" ]; then
MAKECONF_TOOLPREFIX="${bindir}/${MAKECONF_TOOLPREFIX}"
break
fi
done
fi

@shym shym Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is modifying MAKECONF_TOOLPREFIX which was set by the user so I think this could have unintended consequences. I wonder if we could make it more robust by using a defined depext so that the path we look for is fixed (this might be a naïve suggestion from a homebrew non-user) and use it when the user asked explicitly for absolute paths with a configure option.

@samoht

samoht commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

@shym thanks, I think I've addressed all your comments.

@samoht
samoht force-pushed the macos-cross branch 4 times, most recently from b399315 to b057f0e Compare July 9, 2026 23:20
samoht added 3 commits July 10, 2026 23:03
On targets where long double is IEEE quad (aarch64), vfprintf's `union arg`
held the float value as long double, so `%f` (and the fmt_fp call) round-tripped
double -> long double -> double through __extenddftf2/__trunctfdf2. That pulls
libgcc (or compiler-rt) soft-float into every freestanding link that uses printf,
even for integer-only formats. fmt_fp already takes a double, so the long double
was pure overhead.

Use double for the field; %L float conversions are treated as double (never used
by OCaml unikernels). aarch64 cross unikernels now link with no libgcc at all
(clang already inlines the atomics).
The aarch64 wrapper linked -lgcc for printf's long-double soft-float
(now gone: printf formats as double) and for GCC's default outline atomics
(-moutline-atomics lowers C11 atomics to __aarch64_* helpers in libgcc,
which also pull __getauxval, absent on a freestanding solo5 target).

Pass -mno-outline-atomics so atomics inline (LL/SC); the link then needs no
compiler runtime, so drop -lgcc and let a pure-LLVM toolchain link aarch64
unikernels.
Accept a macOS/arm64 host. The OCaml cross build uses the llvm- toolprefix, and
--toolchain-use-absolute-path pins the keg-only Homebrew llvm tools (off PATH
when the cross compiler is later used) to absolute paths; other hosts keep bare,
relocatable tool names. Build the nolibc/openlibm archives with the toolprefix
ar/ranlib (Apple's cannot index ELF), and suppress -Wasm-operand-widths in
nolibc (openlibm's aarch64 fenv asm trips clang under -Werror). Patch
Makefile.cross so crossopt does not pass -function-sections to the host .opt
tools (the macOS host ocamlopt is Mach-O and rejects it), keeping it for the
target library so the linker can still garbage-collect unused code. Add a macOS
CI job building the cross-compiler.
samoht added a commit to samoht/solo5 that referenced this pull request Jul 17, 2026
Add a macOS/arm64 host case: configure.sh accepts a clang / ld.lld /
llvm-objcopy ELF cross toolchain and resolves the keg-only Homebrew
llvm-objcopy to an absolute path. solo5-cross-aarch64 cross-compiles with those
tools, declares the llvm + lld depexts, and a CI job builds it on macos-latest.

The toolchain is pure LLVM: no libgcc is bundled. The downstream link needs
none: ocaml-solo5's nolibc no longer pulls long double soft-float and its
wrapper does not pass -lgcc (mirage/ocaml-solo5#171).
samoht added a commit to samoht/solo5 that referenced this pull request Jul 17, 2026
Add a macOS/arm64 host case: configure.sh accepts a clang / ld.lld /
llvm-objcopy ELF cross toolchain and resolves the keg-only Homebrew
llvm-objcopy to an absolute path. solo5-cross-aarch64 cross-compiles with those
tools, declares the llvm + lld depexts, and a CI job builds it on macos-latest.

The toolchain is pure LLVM: no libgcc is bundled. The downstream link needs
none: ocaml-solo5's nolibc no longer pulls long double soft-float and its
wrapper does not pass -lgcc (mirage/ocaml-solo5#171).
shym added a commit to shym/ocaml-solo5 that referenced this pull request Jul 20, 2026
@samoht
samoht marked this pull request as draft July 22, 2026 23:41
@samoht

samoht commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

I'm moving this to draft as @shym as kindly taken over this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants