Add macOS (hvf) and aarch64 support - #308
simongdavies wants to merge 5 commits into
Conversation
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>
7adb221 to
2e3b3d6
Compare
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>
2e3b3d6 to
6791f66
Compare
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>
ludfjig
left a comment
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
it appears not every entry has a arch field. Is this intended? I think adding it to every entry would make sense
| - name: Set up LLVM guest toolchain (macOS) | ||
| if: runner.os == 'macOS' | ||
| shell: bash |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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' }} |
There was a problem hiding this comment.
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," |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| > 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. |
There was a problem hiding this comment.
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
| > 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 |
There was a problem hiding this comment.
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 @@ | |||
| /* | |||
There was a problem hiding this comment.
Are you sure thsi shoudl be fixed here and not cargo-hyperlgith?
| } | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- |
There was a problem hiding this comment.
it would be nice to split this file into per-arch files but not necessary for this pr
Upstream
hyperlight-host0.17.0 already ships the Hypervisor.framework driver,virtual_machine/hvf/, andregs/aarch64/. The remaining work is on thehyperlight-jsside: platform wiring, an architecture-derived guest target, and CI/packaging.Notable Changes
Introduce cfg aliases (
kvm,mshv3,hvf,whp,crashdump,gdb) inbuild.rs, mirroring upstream's semantics so platform code never tests a barefeature = "..."flag. Enablehvfby default; scopelibctocfg(unix).Gate
with_interrupt_retry_delayonany(kvm, mshv3, hvf)rather than the Linux-only drivers.Add a mach-based thread CPU-time backend for macOS.
js-host-apienablesmonitor-cpu-timeunconditionally, 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-hyperlightdrives clang, somath.hfalls back to asizeofdispatch whose never-taken(long double)branch is still code-generated. That is free on x86_64 (80-bit x87) but emits__extenddftf2/__extendsftf2soft-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
crashdumpbuilds are broken onmaintoday — verified by checking out a cleanorigin/maininto a temp worktree and reproducing the identicalE0596. The snapshot helpers take&selfbut callhyperlight-hostAPIs requiring&mut self. Fixed at both call sites and gated on the newcrashdumpalias, which also restricts them to x86_64 in line with upstream.CI and packaging
dep_buildmatrix 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 suitenpm-publishgains anaarch64-apple-darwinbinary onmacos-15, with a newdarwin-arm64npm package.