diff --git a/.github/workflows/Benchmarks.yml b/.github/workflows/Benchmarks.yml index f1b53dc6..662771c6 100644 --- a/.github/workflows/Benchmarks.yml +++ b/.github/workflows/Benchmarks.yml @@ -58,8 +58,8 @@ jobs: - name: Download Wasm Modules uses: actions/download-artifact@v5 with: - name: guest-modules-lts - path: ./x64/${{ matrix.config }} + name: guest-modules-X64-lts + path: ./build/${{ matrix.config }} ### Benchmarks ### diff --git a/.github/workflows/dep_build_wasm_examples.yml b/.github/workflows/dep_build_wasm_examples.yml index 31ebd609..d38be37b 100644 --- a/.github/workflows/dep_build_wasm_examples.yml +++ b/.github/workflows/dep_build_wasm_examples.yml @@ -23,6 +23,15 @@ permissions: jobs: build-docker-image: if: ${{ inputs.docs_only == 'false' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + strategy: + matrix: + arch: + - name: X64 + runner: ubuntu-latest + docker-platform: linux/amd64 + - name: arm64 + runner: ubuntu-24.04-arm + docker-platform: linux/arm64 runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -46,15 +55,18 @@ jobs: with: images: ${{ github.repository_owner }}/wasm-clang-builder - name: Calculate ghcr cache destination + id: ghcr-dest env: CAN_WRITE: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && github.actor != 'dependabot[bot]' }} run: | if [ "$CAN_WRITE" = "true" ]; then echo "CACHE_TO=type=registry,ref=ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:buildcache,mode=max" >> $GITHUB_ENV echo "DO_PUSH=true" >> $GITHUB_ENV + echo "DO_PUSH=false" >> $GITHUB_OUTPUT else echo "CACHE_TO=type=local,dest=/tmp/wasm-clang-builder-cache/" >> $GITHUB_ENV echo "DO_PUSH=false" >> $GITHUB_ENV + echo "DO_PUSH=false" >> $GITHUB_OUTPUT fi - name: Build and push wasm-clang-builder # Only push if not from a fork, not from pull request, and not from dependabot @@ -67,13 +79,36 @@ jobs: build-args: | WASI_SDK_VERSION_FULL=25.0 GCC_VERSION=12 - tags: ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest + platforms: ${{ matrix.arch.docker-platform }} + tags: ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest-${{ matrix.arch.name }} cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:buildcache cache-to: ${{ env.CACHE_TO }} + outputs: + do_push: ${{ steps.ghcr-dest.outputs.DO_PUSH }} - build-wasm-examples: + merge-docker-image: needs: build-docker-image + if: ${{ needs.build-docker-image.outputs.do_push }} runs-on: ubuntu-latest + steps: + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Create manifest + run: | + docker buildx imagetools create -t ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest \ + ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest-X64 \ + ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest-arm64 + + build-wasm-examples: + needs: merge-docker-image + # Required because merge-docker-image is skipped on some runs, + # and a skipped dependency transitively skips all downstream + # jobs. See: https://github.com/actions/runner/issues/2205 + if: ${{ !cancelled() && !failure() }} strategy: matrix: wasmtime_version: @@ -81,14 +116,22 @@ jobs: features: "" - name: latest features: "wasmtime_latest" + arch: + - name: X64 + runner: ubuntu-latest + - name: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.arch.runner }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Remove default clang run: sudo rm /usr/bin/clang + - name: Fake /dev/kvm for ci-setup-workflow action + run: sudo touch /dev/kvm - name: Hyperlight setup workflow - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + uses: hyperlight-dev/ci-setup-workflow@v2.3.0 with: rust-toolchain: "1.94" - name: Set up Docker Buildx @@ -110,6 +153,6 @@ jobs: - name: Upload Wasm Modules (wasmtime ${{ matrix.wasmtime_version.name }}) uses: actions/upload-artifact@v4 with: - name: guest-modules-${{ matrix.wasmtime_version.name }} + name: guest-modules-${{ matrix.arch.name }}-${{ matrix.wasmtime_version.name }} path: | - x64/release/*.aot + build/release/*.aot diff --git a/.github/workflows/dep_rust.yml b/.github/workflows/dep_rust.yml index 98bc3db5..7d909c5c 100644 --- a/.github/workflows/dep_rust.yml +++ b/.github/workflows/dep_rust.yml @@ -39,25 +39,54 @@ jobs: # failed-job rerun does not need to repeat the entire matrix. fail-fast: false matrix: - hypervisor: [hyperv, mshv3, kvm] # hyperv is windows, mshv and kvm are linux - cpu: [amd, intel] + hypervisor: [hyperv, mshv3, kvm, hvf] # hyperv is windows, mshv and kvm are linux + cpu: [amd, intel, apple, microsoft] + arch: [X64, arm64] config: [debug, release] wasmtime: [latest, lts] exclude: # Latest testing: skip debug builds to reduce CI load - wasmtime: latest config: debug - - runs-on: ${{ fromJson( - format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}", "JobId=build-{3}-{4}-{5}-{6}-{7}"]', - matrix.hypervisor == 'hyperv' && 'Windows' || 'Linux', - matrix.hypervisor == 'hyperv' && 'win2025' || matrix.hypervisor == 'mshv3' && 'azlinux3-mshv' || matrix.hypervisor, - matrix.cpu, - matrix.config, - matrix.wasmtime, - github.run_id, - github.run_number, - github.run_attempt)) }} + - cpu: apple + hypervisor: hyperv + - cpu: apple + hypervisor: mshv3 + - cpu: microsoft + hypervisor: mshv3 + - cpu: microsoft + hypervisor: kvm + - cpu: microsoft + hypervisor: hvf + - arch: X64 + hypervisor: hvf + - cpu: amd + arch: arm64 + - cpu: intel + arch: arm64 + - cpu: apple + arch: X64 + - cpu: microsoft + arch: X64 + runs-on: ${{ fromJson(matrix.arch == 'X64' + && format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-{2}", "JobId=build-{3}-{4}-{5}-{6}-{7}"]', + matrix.hypervisor == 'hyperv' && 'Windows' || 'Linux', + matrix.hypervisor == 'hyperv' && 'win2025' || matrix.hypervisor == 'mshv3' && 'azlinux3-mshv' || matrix.hypervisor, + matrix.cpu, + matrix.config, + matrix.wasmtime, + github.run_id, + github.run_number, + github.run_attempt) + || matrix.hypervisor == 'hyperv' + && format('["self-hosted", "Windows", "ARM64", "1ES.Pool=hl-windows-arm", "JobId=build-{0}-{1}-{2}-{3}"]', + matrix.config, + matrix.run_id, + matrix.run_number, + matrix.run_attempt) + || format('["self-hosted", "{0}", "arm64", "{1}"]', + matrix.hypervisor == 'hvf' && 'macos' || 'Linux', + matrix.hypervisor)) }} env: # Features to pass to cargo. # On Windows LTS builds, do not pass any features. @@ -69,7 +98,7 @@ jobs: - uses: actions/checkout@v5 - name: Hyperlight setup - uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + uses: hyperlight-dev/ci-setup-workflow@v2.3.0 with: rust-toolchain: "1.94" @@ -86,11 +115,32 @@ jobs: if: runner.os == 'Windows' run: rustup component add --toolchain nightly-x86_64-pc-windows-msvc rustfmt + - name: Configure ARM64 libclang + if: runner.os == 'Windows' && matrix.arch == 'arm64' + shell: pwsh + run: | + if (-not $env:VSINSTALLDIR) { + throw "Visual Studio developer environment did not set VSINSTALLDIR" + } + $libclang = Join-Path $env:VSINSTALLDIR "VC\Tools\Llvm\ARM64\bin\libclang.dll" + if (-not (Test-Path $libclang)) { + throw "ARM64 libclang not found at '$libclang'" + } + $llvmPath = Split-Path $libclang + "LIBCLANG_PATH=$llvmPath" >> $env:GITHUB_ENV + $llvmPath >> $env:GITHUB_PATH + + - name: Install clang tools (macOS) + if: ${{ runner.os == 'macOS' }} + run: | + brew install llvm + echo "$(brew --prefix llvm)/bin" >> "$GITHUB_PATH" + - name: Download Wasm Modules uses: actions/download-artifact@v5 with: - name: guest-modules-${{ matrix.wasmtime }} - path: ./x64/${{ matrix.config }} + name: guest-modules-${{ matrix.arch }}-${{ matrix.wasmtime }} + path: ./build/${{ matrix.config }} - name: Build Rust component model examples run: | @@ -174,8 +224,8 @@ jobs: - name: Download Wasm Modules uses: actions/download-artifact@v5 with: - name: guest-modules-${{ matrix.wasmtime }} - path: ./x64/release + name: guest-modules-${{ matrix.arch }}-${{ matrix.wasmtime }} + path: ./build/release - name: Download benchmarks from "latest" run: just bench-download ${{ runner.os }} ${{ matrix.hypervisor }} ${{ matrix.cpu }} dev-latest diff --git a/Justfile b/Justfile index 360bf132..5167b1f6 100644 --- a/Justfile +++ b/Justfile @@ -27,8 +27,8 @@ build target=default-target features="": (fmt-check) cargo build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --verbose --profile={{ if target == "debug" {"dev"} else { target } }} mkdir-redist target=default-target: - mkdir {{ mkdir-arg }} x64 - mkdir {{ mkdir-arg }} x64/{{ target }} + mkdir {{ mkdir-arg }} build + mkdir {{ mkdir-arg }} build/{{ target }} compile-wit: wasm-tools component wit ./src/tests/c_guests/wasmsamples/components/runcomponent.wit -w -o ./src/tests/c_guests/wasmsamples/components/runcomponent-world.wasm @@ -42,28 +42,28 @@ build-wasm-examples target=default-target features="": (compile-wit) build-rust-wasm-examples target=default-target features="": (mkdir-redist target) rustup target add wasm32-unknown-unknown cd ./src/tests/rust_guests/rust_wasm_samples && cargo build --target wasm32-unknown-unknown --profile={{ if target == "debug" {"dev"} else { target } }} - cargo run -p hyperlight-wasm-aot compile {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} ./src/tests/rust_guests/rust_wasm_samples/target/wasm32-unknown-unknown/{{ target }}/rust_wasm_samples.wasm ./x64/{{ target }}/rust_wasm_samples.aot + cargo run -p hyperlight-wasm-aot compile {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} ./src/tests/rust_guests/rust_wasm_samples/target/wasm32-unknown-unknown/{{ target }}/rust_wasm_samples.wasm ./build/{{ target }}/rust_wasm_samples.aot build-pulley-rust-wasm-examples target=default-target features="": (mkdir-redist target) rustup target add wasm32-unknown-unknown cd ./src/tests/rust_guests/rust_wasm_samples && cargo build --target wasm32-unknown-unknown --profile={{ if target == "debug" {"dev"} else { target } }} - cargo run -p hyperlight-wasm-aot compile --pulley {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} ./src/tests/rust_guests/rust_wasm_samples/target/wasm32-unknown-unknown/{{ target }}/rust_wasm_samples.wasm ./x64/{{ target }}/rust_wasm_samples.aot + cargo run -p hyperlight-wasm-aot compile --pulley {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} ./src/tests/rust_guests/rust_wasm_samples/target/wasm32-unknown-unknown/{{ target }}/rust_wasm_samples.wasm ./build/{{ target }}/rust_wasm_samples.aot build-rust-component-examples target=default-target features="": (compile-wit) # use cargo component so we don't get all the wasi imports https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#relationship-with-wasm32-wasip2 # we also explicitly target wasm32-unknown-unknown since cargo component might try to pull in wasi imports https://github.com/bytecodealliance/cargo-component/issues/290 rustup target add wasm32-unknown-unknown cd ./src/tests/rust_guests/component_sample && cargo component build --target wasm32-unknown-unknown --profile={{ if target == "debug" {"dev"} else { target } }} - cargo run -p hyperlight-wasm-aot compile {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} --component ./src/tests/rust_guests/component_sample/target/wasm32-unknown-unknown/{{ target }}/component_sample.wasm ./x64/{{ target }}/component_sample.aot + cargo run -p hyperlight-wasm-aot compile {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} --component ./src/tests/rust_guests/component_sample/target/wasm32-unknown-unknown/{{ target }}/component_sample.wasm ./build/{{ target }}/component_sample.aot cd ./src/tests/rust_guests/greeter_sample && cargo component build --target wasm32-unknown-unknown --profile={{ if target == "debug" {"dev"} else { target } }} - cargo run -p hyperlight-wasm-aot compile {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} --component ./src/tests/rust_guests/greeter_sample/target/wasm32-unknown-unknown/{{ target }}/greeter_sample.wasm ./x64/{{ target }}/greeter_sample.aot + cargo run -p hyperlight-wasm-aot compile {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} --component ./src/tests/rust_guests/greeter_sample/target/wasm32-unknown-unknown/{{ target }}/greeter_sample.wasm ./build/{{ target }}/greeter_sample.aot build-pulley-rust-component-examples target=default-target features="": (compile-wit) # use cargo component so we don't get all the wasi imports https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#relationship-with-wasm32-wasip2 # we also explicitly target wasm32-unknown-unknown since cargo component might try to pull in wasi imports https://github.com/bytecodealliance/cargo-component/issues/290 rustup target add wasm32-unknown-unknown cd ./src/tests/rust_guests/component_sample && cargo component build --target wasm32-unknown-unknown --profile={{ if target == "debug" {"dev"} else { target } }} - cargo run -p hyperlight-wasm-aot compile --pulley {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} --component ./src/tests/rust_guests/component_sample/target/wasm32-unknown-unknown/{{ target }}/component_sample.wasm ./x64/{{ target }}/component_sample.aot + cargo run -p hyperlight-wasm-aot compile --pulley {{ if features =~ "gdb" {"--debug"} else {""} }} {{ if features =~ "wasmtime_latest" {"--wasmtime-version latest"} else {""} }} --component ./src/tests/rust_guests/component_sample/target/wasm32-unknown-unknown/{{ target }}/component_sample.wasm ./build/{{ target }}/component_sample.aot check target=default-target: cargo check --profile={{ if target == "debug" {"dev"} else { target } }} @@ -90,14 +90,17 @@ fmt: cd src/hyperlight_wasm_runtime && cargo +nightly fmt -v --all cd src/hyperlight_wasm_macro && cargo +nightly fmt -v --all +clippy-excluded-features := if arch() == "aarch64" { "gdb,trace_guest,crashdump" } else { "" } +clippy-runtime-features := if arch() == "aarch64" { "" } else { ",gdb,trace_guest" } + clippy target=default-target: (check target) - cargo clippy --workspace --exclude hyperlight-wasm-runtime --exclude hyperlight-wasm-macro --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings + cargo clippy --workspace --exclude hyperlight-wasm-runtime --exclude hyperlight-wasm-macro --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --features "$(cargo metadata --format-version 1 --no-deps | jq -r '[.packages[]|.name as$pkg|.features|keys.[]|select(.!="default" and (IN($ARGS.positional[0]//""|split(",")[])|not))|$pkg+"/"+.]|join(",")' --args {{clippy-excluded-features}})" -- -D warnings cd src/tests/rust_guests/rust_wasm_samples && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings cd src/tests/rust_guests/component_sample && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings cd src/tests/rust_guests/greeter_sample && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings # hyperlight_wasm_runtime has mutually exclusive wasmtime features, so we run clippy for each separately with all other features - cd src/hyperlight_wasm_runtime && cargo hyperlight clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --no-default-features --features wasmtime_latest,gdb,trace_guest,pulley -- -D warnings - cd src/hyperlight_wasm_runtime && cargo hyperlight clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --no-default-features --features wasmtime_lts,gdb,trace_guest -- -D warnings + cd src/hyperlight_wasm_runtime && cargo hyperlight clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --no-default-features --features wasmtime_latest{{clippy-runtime-features}},pulley -- -D warnings + cd src/hyperlight_wasm_runtime && cargo hyperlight clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --no-default-features --features wasmtime_lts{{clippy-runtime-features}} -- -D warnings cd src/hyperlight_wasm_macro && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings # TESTING diff --git a/docs/wasm-modules-debugging.md b/docs/wasm-modules-debugging.md index e7a0d7cd..014167c9 100644 --- a/docs/wasm-modules-debugging.md +++ b/docs/wasm-modules-debugging.md @@ -39,7 +39,7 @@ After you attach, you can set breakpoints, step through the code, and inspect va { "id": "program", "type": "promptString", - "default": "x64/debug/hyperlight_wasm_runtime", + "default": "build/debug/hyperlight_wasm_runtime", "description": "Path to the program to debug" }, ], diff --git a/src/examples_common/src/lib.rs b/src/examples_common/src/lib.rs index 43106eca..9fa49d7c 100644 --- a/src/examples_common/src/lib.rs +++ b/src/examples_common/src/lib.rs @@ -20,7 +20,7 @@ use std::path::Path; use hyperlight_host::{Result, new_error}; /// Get the Wasm module called `name` from the standard module location -/// at `$REPO_ROOT/x64/[debug|release]/$name`. +/// at `$REPO_ROOT/build/[debug|release]/$name`. pub fn get_wasm_module_path(name: &str) -> Result { #[cfg(debug_assertions)] let config = "debug"; @@ -32,17 +32,17 @@ pub fn get_wasm_module_path(name: &str) -> Result { .expect("Failed to get CARGO_MANIFEST_DIR or RUST_DIR_FOR_DEBUGGING_TESTS env var") }); - let x64_dir = Path::new(&proj_dir) - .join("../../x64") + let build_dir = Path::new(&proj_dir) + .join("../../build") .join(config) .join(name); - let path = match x64_dir.canonicalize() { + let path = match build_dir.canonicalize() { Ok(path) => path.to_str().unwrap().to_string(), Err(e) => { return Err(new_error!( "Failed to get the path to the Wasm module: {:?} {}", - x64_dir, + build_dir, e, )); } diff --git a/src/hyperlight_wasm/Cargo.toml b/src/hyperlight_wasm/Cargo.toml index 70100f95..e5dfe646 100644 --- a/src/hyperlight_wasm/Cargo.toml +++ b/src/hyperlight_wasm/Cargo.toml @@ -112,13 +112,14 @@ serde_json = "1.0" junction = "2" [features] -default = ["function_call_metrics", "kvm", "mshv3"] +default = ["function_call_metrics", "kvm", "mshv3", "hvf"] function_call_metrics = ["hyperlight-host/function_call_metrics"] print_debug = ["hyperlight-host/print_debug"] crashdump = ["hyperlight-host/crashdump"] gdb = ["hyperlight-host/gdb"] kvm = ["hyperlight-host/kvm"] mshv3 = ["hyperlight-host/mshv3"] +hvf = ["hyperlight-host/hvf"] pulley = [] trace_guest = ["hyperlight-host/trace_guest"] # Use latest wasmtime instead of the default LTS version in wasm_runtime diff --git a/src/hyperlight_wasm/benches/benchmarks.rs b/src/hyperlight_wasm/benches/benchmarks.rs index 6f5bafb9..91c10eb6 100644 --- a/src/hyperlight_wasm/benches/benchmarks.rs +++ b/src/hyperlight_wasm/benches/benchmarks.rs @@ -75,7 +75,7 @@ fn get_loaded_wasm_sandbox() -> LoadedWasmSandbox { let wasm_sandbox = sandbox.load_runtime().unwrap(); wasm_sandbox - .load_module("../../x64/release/RunWasm.aot") + .load_module("../../build/release/RunWasm.aot") .unwrap() } diff --git a/src/hyperlight_wasm/benches/benchmarks_components.rs b/src/hyperlight_wasm/benches/benchmarks_components.rs index cdf716ff..da6f0aad 100644 --- a/src/hyperlight_wasm/benches/benchmarks_components.rs +++ b/src/hyperlight_wasm/benches/benchmarks_components.rs @@ -93,7 +93,7 @@ fn get_loaded_wasm_sandbox() -> ( let sb = sandbox.load_runtime().unwrap(); let sb = sb - .load_module("../../x64/release/runcomponent.aot") + .load_module("../../build/release/runcomponent.aot") .unwrap(); (sb, rt) } diff --git a/src/hyperlight_wasm/build.rs b/src/hyperlight_wasm/build.rs index 66b41f63..eda934a2 100644 --- a/src/hyperlight_wasm/build.rs +++ b/src/hyperlight_wasm/build.rs @@ -20,7 +20,7 @@ limitations under the License. // This is done by reading the hyperlight-wasm-runtime binary into a static byte array named WASM_RUNTIME. // this build script writes the code to do that to a file named built.rs in the OUT_DIR. // this file is included in lib.rs. -// The hyperlight-wasm-runtime binary is expected to be in the x64/{config} directory. +// The hyperlight-wasm-runtime binary is expected to be in the build/{config} directory. use std::fs::OpenOptions; use std::io::Write; @@ -167,8 +167,12 @@ fn build_wasm_runtime() -> PathBuf { cmd.status() .unwrap_or_else(|e| panic!("could not run cargo build hyperlight-wasm-runtime: {e:?}")); + let target_arch = format!( + "{}-hyperlight-none", + std::env::var("CARGO_CFG_TARGET_ARCH").unwrap() + ); let resource = target_dir - .join("x86_64-hyperlight-none") + .join(target_arch) .join(profile) .join("hyperlight-wasm-runtime"); diff --git a/src/hyperlight_wasm/examples/tracing-otlp/main.rs b/src/hyperlight_wasm/examples/tracing-otlp/main.rs index 805738e4..be8280bc 100644 --- a/src/hyperlight_wasm/examples/tracing-otlp/main.rs +++ b/src/hyperlight_wasm/examples/tracing-otlp/main.rs @@ -26,7 +26,7 @@ limitations under the License. //! Prerequisites: //! - An OTLP-compatible tracing backend (e.g., OpenTelemetry Collector, Jaeger, //! Zipkin) running and accessible at `http://localhost:4318/v1/traces`. -//! - The `rust_wasm_samples.aot` Wasm module available in the expected path (x64//). +//! - The `rust_wasm_samples.aot` Wasm module available in the expected path (build//). //! - The `trace_guest` feature enabled in the `hyperlight-wasm` crate. //! //! ```sh diff --git a/src/hyperlight_wasm/scripts/build-wasm-examples.bat b/src/hyperlight_wasm/scripts/build-wasm-examples.bat index 5170f8f5..7f7081ca 100644 --- a/src/hyperlight_wasm/scripts/build-wasm-examples.bat +++ b/src/hyperlight_wasm/scripts/build-wasm-examples.bat @@ -1,6 +1,6 @@ @echo off pushd %~dp0\..\..\tests\c_guests\wasmsamples -call :NORMALIZEPATH "..\..\..\..\x64\%1" +call :NORMALIZEPATH "..\..\..\..\build\%1" echo "%ABSPATH%" call compile-wasm.bat "." "%ABSPATH%" popd diff --git a/src/hyperlight_wasm/scripts/build-wasm-examples.sh b/src/hyperlight_wasm/scripts/build-wasm-examples.sh index 150644b9..8bc44417 100755 --- a/src/hyperlight_wasm/scripts/build-wasm-examples.sh +++ b/src/hyperlight_wasm/scripts/build-wasm-examples.sh @@ -5,7 +5,7 @@ set -o nounset set -o pipefail pushd "$(dirname "${BASH_SOURCE[0]}")/../../tests/c_guests/wasmsamples" -OUTPUT_DIR="../../../../x64/${1:-"debug"}" +OUTPUT_DIR="../../../../build/${1:-"debug"}" BUILD_TYPE="${1:-"debug"}" FEATURES="${2:-""}" mkdir -p ${OUTPUT_DIR} @@ -36,7 +36,12 @@ else AOT_VERSION_FLAGS="" fi -if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then +if [[ "$CI" == "true" && "$RUNNER_ARCH" == "ARM64" ]]; then + # ARM64 gh runners do not have docker installed on the test + # runners, but will have already downloaded the built examples + # artifact from the builder job + echo "Running in GH Actions on ARM64, so not re-building examples" +elif [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then # running in a container so use the installed wasi-sdk as the devcontainer has this installed for FILENAME in $(find . -name '*.c' -not -path './components/*') do @@ -68,13 +73,16 @@ if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then cargo run ${AOT_FEATURES} -p hyperlight-wasm-aot compile ${AOT_DEBUG_FLAGS} ${AOT_VERSION_FLAGS} --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot done -else +else # not running in a container so use the docker image to build the wasm files echo Building docker image that has Wasm sdk. Should be quick if preivoulsy built and no changes to dockerfile. echo This will take a while if it is the first time you are building the docker image. echo Log in ${OUTPUT_DIR}/dockerbuild.log - docker pull ghcr.io/hyperlight-dev/wasm-clang-builder:latest + arch="$(uname -m)" + if [[ "$arch" == "x86_64" ]]; then + docker pull ghcr.io/hyperlight-dev/wasm-clang-builder:latest + fi docker build --build-arg GCC_VERSION=12 --build-arg WASI_SDK_VERSION_FULL=25.0 --cache-from ghcr.io/hyperlight-dev/wasm-clang-builder:latest -t wasm-clang-builder:latest . 2> ${OUTPUT_DIR}/dockerbuild.log diff --git a/src/hyperlight_wasm/src/sandbox/wasm_sandbox.rs b/src/hyperlight_wasm/src/sandbox/wasm_sandbox.rs index 4ead108a..324f89fc 100644 --- a/src/hyperlight_wasm/src/sandbox/wasm_sandbox.rs +++ b/src/hyperlight_wasm/src/sandbox/wasm_sandbox.rs @@ -696,7 +696,7 @@ mod tests { .expect("Failed to get CARGO_MANIFEST_DIR or RUST_DIR_FOR_DEBUGGING_TESTS env var") }); - let relative_path = "../../x64"; + let relative_path = "../../build"; let filename_path = Path::new(&proj_dir) .join(relative_path) diff --git a/src/hyperlight_wasm_aot/src/main.rs b/src/hyperlight_wasm_aot/src/main.rs index cb0ac8f9..0b39d404 100644 --- a/src/hyperlight_wasm_aot/src/main.rs +++ b/src/hyperlight_wasm_aot/src/main.rs @@ -27,6 +27,7 @@ use wasmtime::{Config, Engine, Module, OptLevel, Precompiled}; #[derive(Debug)] enum SupportedTarget { X86_64UnknownNone, + AArch64UnknownNone, WasmtimePulley64, } @@ -34,6 +35,7 @@ impl Display for SupportedTarget { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SupportedTarget::X86_64UnknownNone => write!(f, "x86_64-unknown-none"), + SupportedTarget::AArch64UnknownNone => write!(f, "aarch64-unknown-none"), SupportedTarget::WasmtimePulley64 => write!(f, "pulley64"), } } @@ -112,7 +114,10 @@ fn precompile_bytes_lts( if pulley { config.target("pulley64").unwrap(); } else { + #[cfg(target_arch = "x86_64")] config.target("x86_64-unknown-none").unwrap(); + #[cfg(target_arch = "aarch64")] + config.target("aarch64-unknown-none").unwrap(); } if debug { config.debug_info(true); @@ -133,7 +138,10 @@ fn precompile_bytes_lts( /// Detect and deserialize using the LTS wasmtime version fn detect_and_deserialize_lts(bytes: &[u8], debug: bool, file: &str) { let mut config = wasmtime_lts::Config::new(); + #[cfg(target_arch = "x86_64")] config.target("x86_64-unknown-none").unwrap(); + #[cfg(target_arch = "aarch64")] + config.target("aarch64-unknown-none").unwrap(); if debug { config.debug_info(true); config.cranelift_opt_level(wasmtime_lts::OptLevel::None); @@ -189,7 +197,11 @@ fn main() { let target = if pulley { SupportedTarget::WasmtimePulley64 } else { - SupportedTarget::X86_64UnknownNone + #[cfg(target_arch = "x86_64")] + let target = SupportedTarget::X86_64UnknownNone; + #[cfg(target_arch = "aarch64")] + let target = SupportedTarget::AArch64UnknownNone; + target }; if debug { println!( @@ -216,7 +228,14 @@ fn main() { let target_name = if pulley { "pulley64" } else { - "x86_64-unknown-none" + #[cfg(target_arch = "x86_64")] + { + "x86_64-unknown-none" + } + #[cfg(target_arch = "aarch64")] + { + "aarch64-unknown-none" + } }; if debug { println!( @@ -356,6 +375,9 @@ fn get_config(debug: bool, minimal: bool, target: &SupportedTarget) -> Config { // See https://github.com/bytecodealliance/wasmtime/pull/11553 unsafe { config.x86_float_abi_ok(true) }; } + SupportedTarget::AArch64UnknownNone => { + config.target("aarch64-unknown-none").unwrap(); + } SupportedTarget::WasmtimePulley64 => { config.target("pulley64").unwrap(); } @@ -390,9 +412,7 @@ fn get_aot_target(bytes: &[u8]) -> Result { if let Ok(elf) = ElfFile64::::parse(bytes) { match elf.architecture() { Architecture::X86_64 => Ok(SupportedTarget::X86_64UnknownNone), - Architecture::Aarch64 => { - Err("Unsupported architecture Aarch64 in AOT compiled file".to_string()) - } + Architecture::Aarch64 => Ok(SupportedTarget::AArch64UnknownNone), Architecture::S390x => { Err("Unsupported architecture S390x in AOT compiled file".to_string()) } diff --git a/src/hyperlight_wasm_runtime/src/component.rs b/src/hyperlight_wasm_runtime/src/component.rs index 3e871445..d5efe84e 100644 --- a/src/hyperlight_wasm_runtime/src/component.rs +++ b/src/hyperlight_wasm_runtime/src/component.rs @@ -108,8 +108,6 @@ fn load_wasm_module_phys(function_call: FunctionCall) -> Result { #[no_mangle] #[instrument(skip_all, level = "Info")] pub extern "C" fn hyperlight_main() { - platform::register_page_fault_handler(); - let mut config = Config::new(); // Enable x86_float_abi_ok only for the latest Wasmtime native x86 target. // Safety: diff --git a/src/hyperlight_wasm_runtime/src/module.rs b/src/hyperlight_wasm_runtime/src/module.rs index c28fae78..331eecf7 100644 --- a/src/hyperlight_wasm_runtime/src/module.rs +++ b/src/hyperlight_wasm_runtime/src/module.rs @@ -248,8 +248,6 @@ fn load_wasm_module_phys(function_call: FunctionCall) -> Result { #[no_mangle] #[instrument(skip_all, level = "Info")] pub extern "C" fn hyperlight_main() { - platform::register_page_fault_handler(); - register_function(GuestFunctionDefinition::new( "PrintOutput".to_string(), vec![ParameterType::String], diff --git a/src/hyperlight_wasm_runtime/src/platform.rs b/src/hyperlight_wasm_runtime/src/platform.rs index 74f3ea90..83ff48be 100644 --- a/src/hyperlight_wasm_runtime/src/platform.rs +++ b/src/hyperlight_wasm_runtime/src/platform.rs @@ -19,87 +19,82 @@ use core::ptr::NonNull; use core::sync::atomic::{AtomicPtr, AtomicU64, Ordering}; use hyperlight_common::vmem; -#[allow(deprecated)] -use hyperlight_guest_bin::exception::arch; -use hyperlight_guest_bin::paging; +use hyperlight_guest_bin::{exception, paging}; + +// Wasmtime Embedding Interface + +const WASMTIME_PROT_READ: u32 = 1 << 0; +const WASMTIME_PROT_WRITE: u32 = 1 << 1; +const WASMTIME_PROT_EXEC: u32 = 1 << 2; + +#[allow(non_camel_case_types)] // we didn't choose the name! +type wasmtime_trap_handler_t = + extern "C" fn(ip: usize, fp: usize, has_faulting_addr: bool, faulting_addr: usize); +static WASMTIME_REQUESTED_TRAP_HANDLER: spin::Mutex> = + spin::Mutex::new(None); + +extern "C" fn page_fault_handler(pc: u64, fp: u64, va: u64) -> ! { + let guard = WASMTIME_REQUESTED_TRAP_HANDLER.lock(); + let handler = *guard; + drop(guard); + if let Some(handler) = handler { + handler(pc as usize, fp as usize, true, va as usize); + } + panic!("wasmtime did not handle page fault"); +} +extern "C" fn undef_handler(pc: u64, fp: u64) -> ! { + let guard = WASMTIME_REQUESTED_TRAP_HANDLER.lock(); + let handler = *guard; + drop(guard); + if let Some(handler) = handler { + handler(pc as usize, fp as usize, false, 0); + } + panic!("wasmtime did not handle undefined instruction"); +} // Extremely stupid virtual address allocator // 0x1_0000_0000 is where the module is // we start at // 0x100_0000_0000 and go up from there static FIRST_VADDR: AtomicU64 = AtomicU64::new(0x100_0000_0000u64); -fn page_fault_handler( - _exception_number: u64, - info: *mut arch::ExceptionInfo, - _ctx: *mut arch::Context, - page_fault_address: u64, -) -> bool { - let error_code = unsafe { (&raw const (*info).error_code).read_volatile() }; - // TODO: check if this is a guard-region trap (which can't happen - // right now since we don't actually set the permissions properly - // in mprotect) - - // TODO: replace this with some generic virtual memory area data - // structure in hyperlight core - if (error_code & 0x1) == 0x0 && page_fault_address >= 0x100_0000_0000u64 { - unsafe { - let phys_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1); - let virt_base = (page_fault_address & !0xFFF) as *mut u8; - paging::map_region( - phys_page, - virt_base, - hyperlight_guest_bin::OS_PAGE_SIZE as u64, - vmem::MappingKind::Basic(vmem::BasicMapping { - readable: true, - writable: true, - executable: true, - }), - ); - virt_base.write_bytes(0u8, hyperlight_guest_bin::OS_PAGE_SIZE as usize); - } - return true; // Try again! - } - false -} -pub(crate) fn register_page_fault_handler() { - // On amd64, vector 14 is #PF - // See AMD64 Architecture Programmer's Manual, Volume 2 - // §8.2 Vectors, p. 245 - // Table 8-1: Interrupt Vector Source and Cause - arch::HANDLERS[14].store( - page_fault_handler as *const () as usize as u64, - Ordering::Release, - ); -} - -// Wasmtime Embedding Interface /* We don't actually have any sensible virtual memory areas, so * we just give out virtual addresses very coarsely with - * probably-more-than-enough space between them, and take over - * page-fault handling to hardcoded check if memory is in this region - * (see above) */ + * probably-more-than-enough space between them */ #[no_mangle] -pub extern "C" fn wasmtime_mmap_new(_size: usize, _prot_flags: u32, ret: &mut *mut u8) -> i32 { - if _size > 0x100_0000_0000 { - panic!("wasmtime_mmap_{:x} {:x}", _size, _prot_flags); +pub extern "C" fn wasmtime_mmap_new(size: usize, prot_flags: u32, ret: &mut *mut u8) -> i32 { + if size > 0x100_0000_0000 { + panic!("wasmtime_mmap_{:x} {:x}", size, prot_flags); } - *ret = FIRST_VADDR.fetch_add(0x100_0000_0000, Ordering::Relaxed) as *mut u8; + let va = FIRST_VADDR.fetch_add(0x100_0000_0000, Ordering::Relaxed) as *mut u8; + wasmtime_mmap_remap(va, size, prot_flags); + *ret = va; 0 } -/* Remap is only used for changing the region size (which is presently - * a no-op, since we just hand out very large regions and treat them all - * the same), or possibly for changing permissions, which will be a no-op - * as we don't properly implement permissions at the moment. */ #[no_mangle] pub extern "C" fn wasmtime_mmap_remap(addr: *mut u8, size: usize, prot_flags: u32) -> i32 { - if size > 0x100_0000_0000 { - panic!( - "wasmtime_mmap_remap {:x} {:x} {:x}", - addr as usize, size, prot_flags - ); + // Since wasmtime needs a huge buffer region (4GB) and we don't + // have hugepages yet, don't actually record anything unless the + // protection flags tell us we need to. One of + // wasmtime_mmap_remap, wasmtime_mprotect, or + // WasmtimeCodeMemory::publish will have a chance to change this + // later. + if prot_flags != 0 { + unsafe { + paging::map_region( + 0, + addr, + size as u64, + vmem::MappingKind::ZeroInit(vmem::BasicMapping { + readable: prot_flags & WASMTIME_PROT_READ != 0, + writable: prot_flags & WASMTIME_PROT_WRITE != 0, + executable: prot_flags & WASMTIME_PROT_EXEC != 0, + }), + ); + } } + paging::barrier::first_valid_same_ctx(); 0 } @@ -108,15 +103,84 @@ pub extern "C" fn wasmtime_munmap(_ptr: *mut u8, _size: usize) -> i32 { 0 } +// On most architectures, "upgrades" including the first time that +// a a valid translation exists for a VA, need a lighter barrier +// than full downgrades, and don't even need that if another +// barrier has been used. +struct BarrierTracker { + downgrade_start: Option, + has_ops_since_last_downgrade: bool, +} +impl BarrierTracker { + fn new() -> Self { + Self { + downgrade_start: None, + has_ops_since_last_downgrade: false, + } + } + fn track(&mut self, here: u64, is_downgrade: bool) { + if is_downgrade && self.downgrade_start.is_none() { + self.downgrade_start = Some(here); + } else if !is_downgrade { + if let Some(start) = self.downgrade_start { + paging::barrier::downgrade_in_place(start..here); + self.downgrade_start = None; + self.has_ops_since_last_downgrade = false; + } else { + self.has_ops_since_last_downgrade = true; + } + } + } +} + /* TODO: implement permissions properly */ #[no_mangle] -pub extern "C" fn wasmtime_mprotect(_ptr: *mut u8, _size: usize, prot_flags: u32) -> i32 { - /* currently all memory is allocated RWX; we assume that - * restricting to R or RX can be ignored */ - if prot_flags == 1 || prot_flags == 3 || prot_flags == 5 { - return 0; +pub extern "C" fn wasmtime_mprotect(ptr: *mut u8, size: usize, prot_flags: u32) -> i32 { + let base_u = ptr as u64; + let mut bt = BarrierTracker::new(); + let readable = prot_flags & WASMTIME_PROT_READ != 0; + let writable = prot_flags & WASMTIME_PROT_WRITE != 0; + let executable = prot_flags & WASMTIME_PROT_EXEC != 0; + unsafe { + paging::modify_mapping(ptr, size as u64, |va, mapping| { + bt.track(va, true); + match mapping.kind { + // The main reason an unmapped region would show up in + // an mprotect'd region is if it was left unmapped by + // wasmtime_mmap_new above, so just treat it + // more-or-less the same as ZeroInit. + vmem::MappingKind::Unmapped => vmem::MappingKind::ZeroInit(vmem::BasicMapping { + readable, + writable, + executable, + }), + vmem::MappingKind::ZeroInit(_) => vmem::MappingKind::ZeroInit(vmem::BasicMapping { + readable, + writable, + executable, + }), + vmem::MappingKind::Basic(_) => vmem::MappingKind::Basic(vmem::BasicMapping { + readable, + writable, + executable, + }), + vmem::MappingKind::Cow(_) if !writable => { + vmem::MappingKind::Basic(vmem::BasicMapping { + readable, + writable, + executable, + }) + } + vmem::MappingKind::Cow(_) => vmem::MappingKind::Cow(vmem::CowMapping { + readable, + executable, + }), + } + }); } - -1 + bt.track(base_u + size as u64, false); + + 0 } #[no_mangle] @@ -124,147 +188,93 @@ pub extern "C" fn wasmtime_page_size() -> usize { unsafe { hyperlight_guest_bin::OS_PAGE_SIZE as usize } } -#[allow(non_camel_case_types)] // we didn't choose the name! -type wasmtime_trap_handler_t = - extern "C" fn(ip: usize, fp: usize, has_faulting_addr: bool, faulting_addr: usize); -static WASMTIME_REQUESTED_TRAP_HANDLER: AtomicU64 = AtomicU64::new(0); -fn wasmtime_trap_handler( - exception_number: u64, - info: *mut arch::ExceptionInfo, - ctx: *mut arch::Context, - _page_fault_address: u64, -) -> bool { - let requested_handler = WASMTIME_REQUESTED_TRAP_HANDLER.load(Ordering::Relaxed); - if requested_handler != 0 { - #[allow(clippy::collapsible_if)] // We will add more cases - if exception_number == 6 { - // #UD - // we assume that handle_trap always longjmp's away, so don't bother - // setting up a terribly proper stack frame - unsafe { - let orig_rip = (&raw mut (*info).rip).read_volatile(); - (&raw mut (*info).rip).write_volatile(requested_handler); - // TODO: This only works on amd64 sysv - (&raw mut (*ctx).gprs[9]).write_volatile(orig_rip); - let orig_rbp = (&raw mut (*ctx).gprs[8]).read_volatile(); - (&raw mut (*ctx).gprs[10]).write_volatile(orig_rbp); - (&raw mut (*ctx).gprs[11]).write_volatile(0); - (&raw mut (*ctx).gprs[12]).write_volatile(0); - } - return true; - } - // TODO: Add handlers for any other traps that wasmtime needs - } - false -} - #[no_mangle] pub extern "C" fn wasmtime_init_traps(handler: wasmtime_trap_handler_t) -> i32 { - WASMTIME_REQUESTED_TRAP_HANDLER.store(handler as usize as u64, Ordering::Relaxed); - // On amd64, vector 6 is #UD - // See AMD64 Architecture Programmer's Manual, Volume 2 - // §8.2 Vectors, p. 245 - // Table 8-1: Interrupt Vector Source and Cause - arch::HANDLERS[6].store( - wasmtime_trap_handler as *const () as usize as u64, - Ordering::Release, - ); + *WASMTIME_REQUESTED_TRAP_HANDLER.lock() = Some(handler); + exception::register_page_fault_handler(page_fault_handler); + exception::register_undefined_instruction_handler(undef_handler); // TODO: Add handlers for any other traps that wasmtime needs, // probably including at least some floating-point // exceptions - // TODO: Ensure that invalid accesses to mprotect()'d regions also - // need to trap, although those will need to go through the - // page fault handler instead of using this handler that - // takes over the exception. 0 } +enum RemapOriginal { + MakeCow, + DontChange, +} + +enum ModifyCopy { + MakeWritable, + DontChange, +} + // Copy a VA range to a new VA. Old and new VA, and len, must be // page-aligned. -fn copy_va_mapping(base: *const u8, len: usize, to_va: *mut u8, remap_original: bool) { +fn copy_va_mapping( + base: *const u8, + len: usize, + to_va: *mut u8, + remap_original: RemapOriginal, + force_writable: ForceWritable, +) { debug_assert!((base as usize).is_multiple_of(vmem::PAGE_SIZE)); debug_assert!(len.is_multiple_of(vmem::PAGE_SIZE)); - // TODO: all this barrier code is amd64 specific. It should be - // refactored to use some better architecture-independent APIs. - // - // On amd64, "upgrades" including the first time that a a valid - // translation exists for a VA, only need a light (serialising - // instruction) barrier. Since invlpg is also a barrier, we don't - // even need that, if we did do a downgrade remap just before. - let mut needs_first_valid_exposure_barrier = false; - - // TODO: make this more efficient by directly exposing the ability - // to traverse an entire VA range in - // hyperlight_guest_bin::paging::virt_to_phys, and coalescing - // continuous ranges there. let base_u = base as u64; - let va_page_bases = (base_u..(base_u + len as u64)).step_by(vmem::PAGE_SIZE); - let mappings = va_page_bases.flat_map(paging::virt_to_phys); - for mapping in mappings { - // TODO: Deduplicate with identical logic in hyperlight_host snapshot. - let (new_kind, was_writable) = match mapping.kind { - // Skip unmapped pages, since they will be unmapped in - // both the original and the new copy - vmem::MappingKind::Unmapped => continue, - vmem::MappingKind::ZeroInit(bm) => (vmem::MappingKind::ZeroInit(bm), false), - vmem::MappingKind::Basic(bm) if bm.writable => ( - vmem::MappingKind::Cow(vmem::CowMapping { - readable: bm.readable, - executable: bm.executable, - }), - true, - ), - vmem::MappingKind::Basic(bm) => ( - vmem::MappingKind::Basic(vmem::BasicMapping { - readable: bm.readable, - writable: false, - executable: bm.executable, - }), - false, - ), - vmem::MappingKind::Cow(cm) => (vmem::MappingKind::Cow(cm), false), - }; - let do_downgrade = remap_original && was_writable; - if do_downgrade { - // If necessary, remap the original page as Cow, instead - // of whatever it is now, to ensure that any more writes to - // that region do not change the image base. - // - // TODO: could the table traversal needed for this be fused - // with the table traversal that got the original mapping, - // above? - unsafe { - paging::map_region( - mapping.phys_base, - mapping.virt_base as *mut u8, - vmem::PAGE_SIZE as u64, - new_kind, - ); - } - } - // map the same pages to the new VA - unsafe { + + let mut bt = BarrierTracker::new(); + + unsafe { + paging::modify_mapping(base as *mut u8, len as u64, |va, mapping| { + // TODO: Deduplicate with identical logic in hyperlight_host snapshot. + let (new_kind, was_writable) = match mapping.kind { + // Skip unmapped pages, since they will be unmapped in + // both the original and the new copy + vmem::MappingKind::Unmapped => return vmem::MappingKind::Unmapped, + vmem::MappingKind::ZeroInit(bm) => (vmem::MappingKind::ZeroInit(bm), false), + vmem::MappingKind::Basic(bm) if bm.writable => ( + vmem::MappingKind::Cow(vmem::CowMapping { + readable: bm.readable, + executable: bm.executable, + }), + true, + ), + vmem::MappingKind::Basic(bm) if force_writable == ForceWritable::MakeWritable => ( + vmem::MappingKind::Cow(vmem::CowMapping { + readable: bm.readable, + executable: bm.executable, + }), + false, + ), + vmem::MappingKind::Basic(bm) => ( + vmem::MappingKind::Basic(vmem::BasicMapping { + readable: bm.readable, + writable: false, + executable: bm.executable, + }), + false, + ), + vmem::MappingKind::Cow(cm) => (vmem::MappingKind::Cow(cm), false), + }; + // map the same pages to the new VA paging::map_region( - mapping.phys_base, - to_va.wrapping_add((mapping.virt_base - base_u) as usize), - vmem::PAGE_SIZE as u64, + mapping.phys_base + (va - mapping.virt_base), + to_va.wrapping_add((va - base_u) as usize), + mapping.len, new_kind, ); - } - if do_downgrade { - // Since we have downgraded a page from writable to CoW we - // need to do an invlpg on it. Because invlpg is a - // serialising instruction, we don't need the other - // barrier for the new mapping. - unsafe { - core::arch::asm!("invlpg [{}]", in(reg) mapping.virt_base, options(readonly, nostack, preserves_flags)); + let do_downgrade = (remap_original == RemapOriginal::MakeCow) && was_writable; + bt.track(va, do_downgrade); + if do_downgrade { + new_kind + } else { + mapping.kind } - needs_first_valid_exposure_barrier = false; - } else { - needs_first_valid_exposure_barrier = true; - } + }); } - if needs_first_valid_exposure_barrier { + + bt.track(base_u + len as u64, false); + if bt.has_ops_since_last_downgrade { paging::barrier::first_valid_same_ctx(); } } @@ -283,7 +293,13 @@ pub extern "C" fn wasmtime_memory_image_new( // the original VA range here, making the original copy CoW as we // go. let new_virt = FIRST_VADDR.fetch_add(0x100_0000_0000, Ordering::Relaxed) as *mut u8; - copy_va_mapping(ptr, len, new_virt, true); + copy_va_mapping( + ptr, + len, + new_virt, + RemapOriginal::MakeCow, + ModifyCopy::DontChange + ); *ret = new_virt as *mut c_void; 0 } @@ -294,7 +310,13 @@ pub extern "C" fn wasmtime_memory_image_map_at( addr: *mut u8, len: usize, ) -> i32 { - copy_va_mapping(image as *mut u8, len, addr, false); + copy_va_mapping( + image as *mut u8, + len, + addr, + RemapOriginal::DontChange, + ModifyCopy::MakeWritable + ); 0 } @@ -340,16 +362,22 @@ impl wasmtime::CustomCodeMemory for WasmtimeCodeMemory { } fn publish_executable( &self, - _ptr: *const u8, - _len: usize, + ptr: *const u8, + len: usize, ) -> core::result::Result<(), wasmtime::Error> { + wasmtime_mprotect(ptr as *mut u8, len, WASMTIME_PROT_READ | WASMTIME_PROT_EXEC); Ok(()) } fn unpublish_executable( &self, - _ptr: *const u8, - _len: usize, + ptr: *const u8, + len: usize, ) -> core::result::Result<(), wasmtime::Error> { + wasmtime_mprotect( + ptr as *mut u8, + len, + WASMTIME_PROT_READ | WASMTIME_PROT_WRITE, + ); Ok(()) } } diff --git a/src/tests/c_guests/wasmsamples/dockerfile b/src/tests/c_guests/wasmsamples/dockerfile index 1e3fde02..b1fe04be 100644 --- a/src/tests/c_guests/wasmsamples/dockerfile +++ b/src/tests/c_guests/wasmsamples/dockerfile @@ -3,21 +3,25 @@ FROM ubuntu ENV DEBIAN_FRONTEND=noninteractive LABEL org.opencontainers.image.source=https://github.com/hyperlight-dev/hyperlight-wasm +ARG TARGETPLATFORM ARG GCC_VERSION=12 RUN apt-get update \ && apt-get upgrade -y RUN apt-get install -y wget \ && apt-get install -y build-essential \ - && apt-get install -y g++-multilib \ && apt-get install -y libgcc-${GCC_VERSION}-dev \ - && apt-get install -y lib32gcc-${GCC_VERSION}-dev + && ([[ $TARGETPLATFORM == "linux/amd64" ]] \ + && (apt-get install -y g++-multilib \ + && apt-get install -y libgcc32-${GCC_VERSION}-dev) \ + || true) ARG WASI_SDK_VERSION_FULL=25.0 ARG WASI_SDK_VERSION_MAJOR=${WASI_SDK_VERSION_FULL%%.*} -RUN wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION_MAJOR}/wasi-sdk-${WASI_SDK_VERSION_FULL}-x86_64-linux.tar.gz \ - && tar xvf wasi-sdk-${WASI_SDK_VERSION_FULL}-x86_64-linux.tar.gz \ - && rm wasi-sdk-${WASI_SDK_VERSION_FULL}-x86_64-linux.tar.gz \ - && mv /wasi-sdk-${WASI_SDK_VERSION_FULL}-x86_64-linux /opt/wasi-sdk +RUN WASI_SDK_ARCH=$(echo "${TARGETPLATFORM#*/}" | sed 's/amd64/x86_64/'); \ + wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION_MAJOR}/wasi-sdk-${WASI_SDK_VERSION_FULL}-${WASI_SDK_ARCH}-linux.tar.gz \ + && tar xvf wasi-sdk-${WASI_SDK_VERSION_FULL}-${WASI_SDK_ARCH}-linux.tar.gz \ + && rm wasi-sdk-${WASI_SDK_VERSION_FULL}-${WASI_SDK_ARCH}-linux.tar.gz \ + && mv /wasi-sdk-${WASI_SDK_VERSION_FULL}-${WASI_SDK_ARCH}-linux /opt/wasi-sdk CMD ["/bin/sh"]