Skip to content

Add macOS (hvf) and aarch64 support - #308

Open
simongdavies wants to merge 5 commits into
mainfrom
simongdavies-macos-support
Open

simongdavies wants to merge 5 commits into
mainfrom
simongdavies-macos-support

Conversation

@simongdavies

@simongdavies simongdavies commented Sep 16, 2026

Copy link
Copy Markdown
Member

Upstream hyperlight-host 0.17.0 already ships the Hypervisor.framework driver, virtual_machine/hvf/, and regs/aarch64/. The remaining work is on the hyperlight-js side: platform wiring, an architecture-derived guest target, and CI/packaging.

Stack: this is the bottom layer of a planned stack. Layer 2 (npm linux-arm64 packaging) follows, and #295 is intended to be rebased on top and appended retrospectively. Review/merge this one first.

Notable Changes

  • Introduce cfg aliases (kvm, mshv3, hvf, whp, crashdump, gdb) in build.rs, mirroring upstream's semantics so platform code never tests a bare feature = "..." flag. Enable hvf by default; scope libc to cfg(unix).

  • Gate with_interrupt_retry_delay on any(kvm, mshv3, hvf) rather than the Linux-only drivers.

  • Add a mach-based thread CPU-time backend for macOS. js-host-api enables monitor-cpu-time unconditionally, so this must compile on every host platform.

  • Add src/hyperlight-js-runtime/include/math.h. This is the one non-obvious change and it is what unblocks aarch64 guest builds:

    newlib guards its __builtin_* fast path with … && !defined(__clang__). cargo-hyperlight drives clang, so math.h falls back to a sizeof dispatch whose never-taken (long double) branch is still code-generated. That is free on x86_64 (80-bit x87) but emits __extenddftf2/__extendsftf2 soft-float libcalls on aarch64 (binary128), which fail to link against the guest sysroot. The shim uses #include_next <math.h> and restores the builtin path under clang.

Pre-existing crashdump build failure

crashdump builds are broken on main today — verified by checking out a clean origin/main into a temp worktree and reproducing the identical E0596. The snapshot helpers take &self but call hyperlight-host APIs requiring &mut self. Fixed at both call sites and gated on the new crashdump alias, which also restricts them to x86_64 in line with upstream.

CI and packaging

  • dep_build matrix goes 6 → 10 jobs: macOS/hvf on the same self-hosted ["self-hosted","macos","arm64","hvf"] runners hyperlight already uses, plus Linux aarch64 KVM. These run the full test suite
  • npm-publish gains an aarch64-apple-darwin binary on macos-15, with a new darwin-arm64 npm package.

Upstream hyperlight-host 0.17.0 already ships the Hypervisor.framework driver
and aarch64 register support, so the remaining work is on the hyperlight-js
side: platform wiring, an architecture-derived guest target, and CI/packaging.

Hypervisor and platform wiring:
- Introduce cfg aliases (kvm, mshv3, hvf, whp, crashdump, gdb) in build.rs
  mirroring upstream's semantics, so platform code never tests a bare feature
  flag. Enable the `hvf` feature by default and scope `libc` to cfg(unix).
- Gate `with_interrupt_retry_delay` on any(kvm, mshv3, hvf) rather than on the
  Linux-only drivers.
- Add a mach-based thread CPU-time backend for macOS. js-host-api enables
  `monitor-cpu-time` unconditionally, so this has to compile on every host.

Architecture generalisation:
- Derive the guest target triple as `{arch}-hyperlight-none` in build.rs and
  the Justfile instead of hard-coding x86_64.
- Add src/hyperlight-js-runtime/include/math.h. newlib excludes clang from its
  __builtin_* fast path, so math.h falls back to a sizeof dispatch whose
  never-taken `long double` branch is still code-generated. That is free on
  x86_64 (80-bit x87) but emits __extenddftf2/__extendsftf2 soft-float
  libcalls on aarch64, which fail to link against the guest sysroot. The shim
  uses #include_next and restores the builtin path under clang.

Fix a pre-existing crashdump build failure:
- `crashdump` builds are broken on main: the snapshot helpers take &self but
  call hyperlight-host APIs that require &mut self. Take &mut self at both
  call sites and gate them on the new `crashdump` alias, which also restricts
  them to x86_64 in line with upstream.

CI and packaging:
- Extend the dep_build matrix from 6 to 10 jobs, covering macOS/hvf on the
  self-hosted arm64 runners upstream already uses, plus Linux aarch64 KVM.
  These run the full test suite rather than build-only.
- Publish an aarch64-apple-darwin binary and add the darwin-arm64 npm package.

Not yet exercised on real macOS or Linux aarch64 hardware; CI is the first run.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
@simongdavies simongdavies added the kind/enhancement New feature or improvement label Sep 16, 2026
@simongdavies
simongdavies force-pushed the simongdavies-macos-support branch from 7adb221 to 2e3b3d6 Compare September 16, 2026 18:47
The self-hosted Mac minis are shared and non-ephemeral, so concurrent
jobs race on ~/.rustup/downloads and rustup intermittently dies with
"could not rename 'downloaded' file from <hash>.partial". Skip the
download when llvm-ar is already present and retry with backoff
otherwise, failing loudly if it still cannot be found.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
@simongdavies
simongdavies force-pushed the simongdavies-macos-support branch from 2e3b3d6 to 6791f66 Compare September 16, 2026 18:52
simongdavies and others added 3 commits September 16, 2026 20:04
The macOS job is the first thing that has ever compiled this code, and
clippy runs with -D warnings, so three diagnostics failed the lint step:
a doc comment on an extern block (rustdoc does not document those), and
libc's mach_thread_self / mach_task_self both being deprecated in favour
of the mach2 crate.

Declare mach_thread_self alongside the existing mach_port_deallocate
declaration rather than taking a new dependency for two symbols.
mach_task_self is a C macro over the mach_task_self_ global, so declare
that global directly; it is only ever read, so an immutable extern
static is sufficient. Demote the extern block's doc comment to a plain
comment.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
Every sandbox test failed on macOS with
HyperlightVmError(Create(Vm(CreateVm(CreateVmFd(HvfError(0xfae94007)))))).
0xfae94007 is HV_DENIED: Hypervisor.framework refuses hv_vm_create()
unless the calling process carries com.apple.security.hypervisor, which
is mandatory on Apple Silicon and which plain cargo test binaries do not
have.

Add a cargo target runner for macOS that ad-hoc codesigns each binary
with that entitlement before executing it, mirroring dev/macos-sign-and-run.sh
in hyperlight-dev/hyperlight. Cargo resolves a runner path containing a
separator relative to the config file rather than the working directory,
so a single root .cargo/config.toml also covers the recipes that cd into
src/hyperlight-js. The guest target is unaffected because the runner is
scoped to cfg(target_os = "macos").

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
The cargo runner added in .cargo/config.toml only signs binaries cargo
launches, so the js-host-api suite -- which runs under node via vitest --
still failed with HV_DENIED: 115 failed, 17 passed, the survivors being
the tests that never create a VM.

Copy node into RUNNER_TEMP, ad-hoc sign the copy with the hypervisor
entitlement and prepend it to PATH. npm resolves node through a
/usr/bin/env shebang and entitlements apply per exec, so vitest and its
workers pick the signed copy up. Sign a private copy rather than the
shared tool cache because these runners are not ephemeral.

npm-publish.yml needs no equivalent: it only builds and publishes, and
never creates a VM.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
@simongdavies
simongdavies added this pull request to stack #311 September 16, 2026 20:37
@simongdavies simongdavies added the ready-for-review PR is ready for (re-)review label Sep 16, 2026

@ludfjig ludfjig 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.

LGTM with some questions. Also maybe @syntactically could have a look

matrix:
build: [windows-2025-debug, linux-kvm-debug, linux-hyperv3-debug, windows-2025-release, linux-kvm-release, linux-hyperv3-release]
build: [windows-2025-debug, linux-kvm-debug, linux-kvm-arm64-debug, linux-hyperv3-debug, macos-hvf-debug, windows-2025-release, linux-kvm-release, linux-kvm-arm64-release, linux-hyperv3-release, macos-hvf-release]
include:

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.

it appears not every entry has a arch field. Is this intended? I think adding it to every entry would make sense

Comment on lines +101 to +103
- name: Set up LLVM guest toolchain (macOS)
if: runner.os == 'macOS'
shell: bash

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.

Can we install this while provising runners or similar instead of here? And what's the retried for?

shell: bash
run: |
set -euo pipefail
llvm_bin="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin"

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.

should we allow an existing llvm-ar not related to the rust-toolchain?

- name: Publish macOS arm64 package
if: ${{ !inputs.dry_run }}
working-directory: ${{ env.WORKING_DIR }}/npm/darwin-arm64
run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-darwin-arm64,')) && '' || '--provenance' }}

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.

AI is telling me the (condition) && '' || '--provenance' is buggy and always produces --provenance because '' is falsy

# Comma-delimited npm package names that have not yet been published and
# therefore cannot have a trusted publisher configured. Remove each package
# in a follow-up PR after its first release and trusted-publisher setup.
FIRST_TIME_PACKAGES: ",@hyperlight-dev/js-host-api-darwin-arm64,"

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 makes releases require NPM_TOKEN, but the caller in CreateRelease.yml does not forward secrets. Token validation will fail even if the repository secret exists. Could we also update the caller to pass it through?

NODE_AUTH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.NPM_TOKEN || '' }}
NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-win32-x64-msvc,')) && secrets.NPM_TOKEN || '' }}

- name: Publish macOS arm64 package

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.

Could we validate the macOS package tarball before publishing? test-pack.sh only packs and installs the Linux GNU package. The macOS CI tests exercise the build, but not installation from the release tarball.

Comment thread docs/release.md
> package that does not exist yet. The publish workflow therefore has a temporary
> `FIRST_TIME_PACKAGES` list in `.github/workflows/npm-publish.yml`. Packages in that list use
> the `NPM_TOKEN` repository secret for their first release; all other packages continue to use
> trusted publishing (OIDC). Token-published packages do not receive npm provenance attestations.

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.

Token-based publishing supports provenance. npm's example uses NODE_AUTH_TOKEN together with npm publish --provenance --access public.
https://docs.npmjs.com/generating-provenance-statements#example-github-actions-workflow

Comment thread docs/release.md
> trusted publishing (OIDC). Token-published packages do not receive npm provenance attestations.
>
> For a new package, add its full npm name to `FIRST_TIME_PACKAGES`, ensure the short-lived
> `NPM_TOKEN` secret is available, and release normally from the `CreateRelease` workflow. After

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.

Should we add cleanup instructions for this bootstrap path? After publishing, delete the NPM_TOKEN repository secret and revoke the npm token. Those steps currently appear only in the emergency publishing section.

@@ -0,0 +1,76 @@
/*

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.

Are you sure thsi shoudl be fixed here and not cargo-hyperlgith?

}
}

// ---------------------------------------------------------------------------

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.

it would be nice to split this file into per-arch files but not necessary for this pr

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

Labels

kind/enhancement New feature or improvement ready-for-review PR is ready for (re-)review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants